Menu
CoddyTech

Le gestionnaire 404

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

Express répond à un chemin inconnu avec sa propre page, Cannot GET /x, qui ne correspond ni à votre site ni à votre API.

app.use((req, res) => {
    res.status(404).send('No forecast here. Try /weather/oslo');
});

Un middleware sans chemin s’exécute pour chaque requête qui l’atteint. Placez celui-ci après toutes les routes et après express.static : les requêtes auxquelles elles ont répondu ne l’atteignent jamais, il ne voit donc que celles auxquelles rien n’a répondu. Il répond lui-même, il n’a donc pas besoin de next.

req.path est le chemin sans la chaîne de requête ; ainsi, req.path.startsWith('/api/') permet de distinguer un appel d’API d’une page.

challenge icon

Défi

Facile

Les chemins qui n’existent pas affichent la page toute simple Cannot GET d’Express. Remplace-la par la tienne : du JSON pour l’API et une page accueillante pour les visiteurs.

  1. Dans server.js, après chaque route et après express.static, ajoute un dernier app.use sans chemin. Les requêtes auxquelles un autre élément a déjà répondu ne l’atteignent jamais : il ne voit donc que celles qui n’ont pas été traitées.
  2. Lorsque le chemin commence par /api/, la réponse a le statut 404 et contient ce JSON : {"error": "Not found"}
  3. Pour tout autre chemin, la réponse a le statut 404 et contient une page HTML avec les mots Page not found, par exemple <h1>Page not found</h1>. Ajouter un lien vers la page d’accueil est une bonne idée.

Essaie : appuie sur Run et envoie /api/nope. Tu devrais voir le statut 404 et {"error":"Not found"}. Envoie /nope : statut 404, affiché sous forme d’une page indiquant Page not found. Envoie /api/recipes/1 : statut 200 et Tomato Soup, ce qui montre que tes routes sont toujours traitées en premier.

Essayez vous-même

import { escapeHtml } from './html.js';
import express from 'express';
import { randomUUID } from 'node:crypto';
import { recipes } from './data.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') envoie du texte brut, donc 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.get('/api/recipes', (req, res) => {
    res.json(recipes);
});

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

app.post('/api/recipes', (req, res) => {
    const { title, tags } = req.body;
    if (!title) {
        res.status(400).json({ error: 'A recipe needs a title' });
        return;
    }
    const recipe = { id: recipes.length + 1, title, tags: tags ?? [] };
    recipes.push(recipe);
    res.status(201).json(recipe);
});

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.listen(3000, () => {
    console.log('Recipe Box is running on port 3000');
});

Toutes les leçons de Les bases d’Express

Entraînez-vous par vous-même : Playground Express