Menu
CoddyTech

Página de búsqueda

Parte de la sección Conceptos básicos de Express del Journey de Node.js de Coddy. Lección 23 de 23.

Una página de búsqueda es un formulario, y el servidor responde a ella como a cualquier otra solicitud GET.

<form action="/books" method="get">
    <input name="author">
    <button>Find</button>
</form>

Al enviar este formulario, el navegador va a /books?author= seguido de lo que se escribió en el campo llamado author. La ruta lo lee de req.query. Escribir la palabra de nuevo como value del campo hace que siga visible después de la búsqueda; proviene del visitante, así que escápala.

challenge icon

Desafío

Fácil

Recipe Box incorpora una página de búsqueda. Un visitante escribe una palabra y la página muestra las recetas cuyo título la contiene.

  1. En server.js, encima del controlador 404, añade una ruta asíncrona para /search. Lee la palabra de req.query.q o usa una cadena vacía si no hay ninguna.
  2. La página siempre tiene un formulario de búsqueda: <form action="/search" method="get"> con un campo de texto llamado q y un botón. Al enviarlo, se abre /search?q= seguido de la palabra.
  3. El campo muestra la palabra que se buscó: después de buscar curry, el campo contiene curry. Escribe la palabra en el atributo value del campo, pasándola por escapeHtml, porque proviene del visitante.
  4. Debajo del formulario, muestra un enlace por cada receta de loadRecipes() cuyo título contenga la palabra, sin distinguir entre mayúsculas y minúsculas: tanto soup como SOUP encuentran Tomato Soup. Cada enlace lleva a la página de la receta y muestra su título escapado, como en la página de inicio: <a href="/recipes/1">Tomato Soup</a>.
  5. Cuando se haya buscado una palabra y ningún título la contenga, muestra el texto No recipes found en lugar de la lista.

Pruébalo: Pulsa Ejecutar y envía /search: la página muestra el formulario vacío. Escribe SOUP en el campo y pulsa el botón. La página muestra Tomato Soup y el campo sigue mostrando SOUP. Busca pizza: No recipes found. Busca fish: Fish & Chips, correctamente mostrado. Después, envía por POST {"title": "Pea Soup", "tags": ["soup"]} a /api/recipes y vuelve a buscar soup: Pea Soup también aparece en la lista.

Pruébalo tú mismo

import { escapeHtml } from './html.js';
import { dayOfYear } from './day-of-year.js';
import express from 'express';
import { randomUUID } from 'node:crypto';
import { recipes } from './data.js';
import recipesRouter, { loadRecipes } from './routes/recipes.js';
import reviewsRouter from './routes/reviews.js';

const app = express();

app.use((req, res, next) => {
    console.log(`${req.method} ${req.url}`);
    next();
});

app.use((req, res, next) => {
    res.set('X-Request-Id', randomUUID());
    next();
});

app.use(express.json());

app.use(express.static('public'));

app.get('/', (req, res) => {
    const items = recipes.map((r) => `<li><a href="/recipes/${r.id}">${escapeHtml(r.title)}</a></li>`);
    res.send(`<link rel="stylesheet" href="/style.css"><h1>Recipe Box</h1><ul>${items.join('')}</ul>`);
});

// type('text') envía texto sin formato, por lo que cada título aparece en su propia línea.
app.get('/recipes', (req, res) => {
    const { tag } = req.query;
    const list = tag ? recipes.filter((recipe) => recipe.tags.includes(tag)) : recipes;
    res.type('text').send(list.map((recipe) => recipe.title).join('\n'));
});

app.get('/recipes/:id', (req, res) => {
    const recipe = recipes.find((r) => r.id === Number(req.params.id));
    if (!recipe) {
        res.status(404).send('No such recipe');
        return;
    }
    res.send(recipe.title);
});

app.use('/api/recipes', recipesRouter);
app.use('/api/recipes/:id/reviews', reviewsRouter);

app.get('/api/today', async (req, res) => {
    const day = dayOfYear(req.query.date);
    if (day === null) {
        res.status(400).json({ error: 'Use a date like 2026-03-01' });
        return;
    }
    const recipes = await loadRecipes();
    res.json(recipes[day % recipes.length]);
});

app.get('/about', (req, res) => {
    res.send('Recipe Box collects family recipes.');
});

app.get('/menu', (req, res) => {
    res.redirect(301, '/recipes');
});

app.get('/menu/:id', (req, res) => {
    res.redirect(301, `/recipes/${req.params.id}`);
});

app.use((req, res) => {
    if (req.path.startsWith('/api/')) {
        res.status(404).json({ error: 'Not found' });
        return;
    }
    res.status(404).send('<h1>Page not found</h1><p><a href="/">Back to Recipe Box</a></p>');
});

app.use((err, req, res, next) => {
    console.error(err);
    res.status(500).json({ error: 'Something went wrong' });
});

app.listen(3000, () => {
    console.log('Recipe Box is running on port 3000');
});

Todas las lecciones de Conceptos básicos de Express

Practica por tu cuenta: Playground de Express