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.
Aufgabe
EinfachFahre 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.
- Füge in
server.jsoberhalb des 404-Handlers eine Route für/api/recipes/:id/scalehinzu. Sie liest den Faktor aus der Abfragezeichenfolge aus, wie in/api/recipes/1/scale?by=2. - Wenn kein Rezept diese ID hat, antworte mit Status 404 und
{"error": "No such recipe"}. - Wandle
byin 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')istNaN, undNumber.isNaNerkennt das. - Wenn das Rezept keine
servingshat, wirf einen neuenError, dessen MeldungRecipe 5 has no servingslautet; ersetze dabei die 5 durch die ID des Rezepts. - 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. - 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 mitconsole.erroraus 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');
});
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