Menu
CoddyTech

Der 404-Handler

Teil des Abschnitts Express-Grundlagen der Node.js-Journey von Coddy. Lektion 14 von 20.

Express beantwortet einen unbekannten Pfad mit einer eigenen Seite, Cannot GET /x, die weder zu deiner Website noch zu deiner API passt.

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

Eine Middleware ohne Pfad wird bei jeder Anfrage ausgeführt, die sie erreicht. Setze sie nach jeder Route und nach express.static: Anfragen, die dort beantwortet wurden, gelangen nie hierher, sodass sie nur die Anfragen sieht, auf die nichts geantwortet hat. Sie beantwortet die Anfrage, daher braucht sie kein next.

req.path ist der Pfad ohne die Abfragezeichenfolge, daher unterscheidet req.path.startsWith('/api/') einen API-Aufruf von einer Seite.

challenge icon

Aufgabe

Einfach
  • Ein Pfad unter /api/, auf den nichts antwortet, liefert 404 mit { "error": "Not found" }.
  • Jeder andere Pfad liefert eine HTML-Seite mit dem Statuscode 404 und der Meldung Page not found.

Sende GET /api/nope, öffne /nope in der Seitenansicht und sende dann GET /api/recipes/1.

Probier es selbst

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

const escapeHtml = (text) =>
    text
        .replaceAll('&', '&')
        .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') sendet einfachen Text, sodass jeder Titel in einer eigenen Zeile erscheint.
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');
});

Alle Lektionen in Express-Grundlagen