Menu
CoddyTech

Fehlerbehandlung

Teil des Abschnitts Express-Grundlagen der Node.js-Journey von Coddy. Lektion 18 von 23.

Wenn eine Route einen Fehler auslöst, antwortet Express mit einer eigenen Fehlerseite, die kein JSON ist und Details anzeigt, die für dich bestimmt sind.

app.get('/api/fines/:member', (req, res) => {
    throw new Error('Fines database is down');
});

app.use((err, req, res, next) => {
    console.error(err);
    res.status(500).json({ error: 'Try again later' });
});

Eine Middleware mit vier Argumenten, (err, req, res, next), ist ein Fehlerhandler. Express zählt die Argumente: Bei drei Argumenten handelt es sich um gewöhnliche Middleware, und Fehler überspringen sie. Wenn eine Route einen Fehler auslöst, überspringt Express die gewöhnliche Middleware und ruft den Fehlerhandler mit dem Fehler auf. Füge ihn nach dem 404-Handler als Letztes hinzu. Protokolliere die Details für dich selbst und sende dem Client eine kurze Nachricht.

throw new Error('text') beendet die Route und übergibt den Fehler an Express; dasselbe bewirkt der Aufruf von next(err). Antworte mit 400, wenn der Besucher etwas Falsches gesendet hat, das er korrigieren kann. Wirf einen Fehler, wenn der Server schuld ist.

challenge icon

Aufgabe

Einfach

Fahre mit Recipe Box fort. Die separaten Dateien in practice-errors/ kannst du ignorieren; „Run“ startet jetzt den Server server.js des Projekts.

Köche möchten ein Rezept hochskalieren, zum Beispiel die Portionen für eine Party verdoppeln. Ein Rezept, das ohne Portionen gespeichert wurde, lässt sich nicht hochskalieren. Das ist ein Fehler in deinen Daten und nichts, was der Besucher beheben kann. Daher wird der Fehler an einen Fehlerhandler weitergeleitet, der eine saubere JSON-Antwort mit Status 500 sendet.

  1. Füge in server.js oberhalb des 404-Handlers eine Route für /api/recipes/:id/scale hinzu. Sie liest den Faktor aus der Abfragezeichenfolge aus, wie in /api/recipes/1/scale?by=2.
  2. Wenn kein Rezept diese ID hat, antworte mit Status 404 und {"error": "No such recipe"}.
  3. Wandle by in eine Zahl um. Wenn der Wert keine Zahl ist oder 0 oder kleiner, antworte mit Status 400 und {"error": "Scale by a positive number"}. Number('lots') ist NaN, und Number.isNaN erkennt das.
  4. Wenn das Rezept keine servings hat, wirf einen neuen Error, dessen Meldung Recipe 5 has no servings lautet; ersetze dabei die 5 durch die ID des Rezepts.
  5. Antworte andernfalls mit JSON, das nur den Titel und die hochgerechneten Portionen enthält. Rezept 1, Tomato Soup, hat 4 Portionen. Daher sendet /api/recipes/1/scale?by=2 {"title": "Tomato Soup", "servings": 8}. Füge keine weiteren Felder hinzu.
  6. Füge am Ende der Middleware der Datei, nach dem 404-Handler, einen Fehlerhandler hinzu: ein app.use, dessen Funktion vier Argumente entgegennimmt, (err, req, res, next). Er gibt den Fehler mit console.error aus und antwortet mit Status 500 und {"error": "Something went wrong"}.

Probiere es aus: Drücke „Run“ und sende /api/recipes/1/scale?by=2. Du solltest {"title":"Tomato Soup","servings":8} sehen. Sende /api/recipes/2/scale?by=lots: Status 400 und {"error":"Scale by a positive number"}. Sende /api/recipes/99/scale?by=2: Status 404. Wähle jetzt POST und sende {"title": "Toast"} an /api/recipes. Daraus wird Rezept 5, ohne Portionen. Sende /api/recipes/5/scale?by=2: Status 500 und {"error":"Something went wrong"}; in der Konsole wird der Fehler Recipe 5 has no servings angezeigt.

Probier es selbst

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') sendet reinen Text, sodass jeder Titel in einer eigenen Zeile steht.
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.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.listen(3000, () => {
    console.log('Recipe Box is running on port 3000');
});
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Express-Grundlagen

Übe selbstständig: Express-Playground