Menu
CoddyTech

Page de recherche

Fait partie de la section Les bases d’Express du Journey Node.js de Coddy. Leçon 20 sur 20.

Une page de recherche est un formulaire, et le serveur y répond comme à toute autre requête GET.

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

La soumission de ce formulaire envoie le navigateur vers /books?author=, suivi du texte saisi dans le champ nommé author. La route le lit dans req.query. Réécrire le mot dans l’attribut value du champ permet de le garder visible après la recherche ; il provient du visiteur, alors échappez-le.

challenge icon

Défi

Facile
  • GET /search?q=soup affiche une page avec un formulaire GET (champ q, envoyé à /search) et des liens vers les titres contenant le mot, quelle que soit la casse, issus de loadRecipes().
  • Aucune correspondance : No recipes found.
  • Le champ conserve le mot, échappé.

Ouvre /search dans l’affichage de la page et recherche SOUP, puis pizza.

Essayez vous-même

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'));

const escapeHtml = (text) =>
    text
        .replaceAll('&', '&amp;')
        .replaceAll('<', '&lt;')
        .replaceAll('>', '&gt;')
        .replaceAll('"', '&quot;');

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') envoie du texte brut, de sorte que chaque titre apparaît sur sa propre ligne.
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 date = req.query.date ? new Date(req.query.date) : new Date();
    if (Number.isNaN(date.getTime())) {
        res.status(400).json({ error: 'Use a date like 2026-03-01' });
        return;
    }
    const day = Math.floor((date - Date.UTC(date.getUTCFullYear(), 0, 0)) / 86400000);
    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');
});

Toutes les leçons de Les bases d’Express