Menu
CoddyTech

Obsługa błędów

Część sekcji Podstawy Express ścieżki Node.js w Coddy. Lekcja 18 z 23.

Gdy trasa zgłasza wyjątek, Express wyświetla własną stronę błędu, która nie jest JSON-em i pokazuje szczegóły przeznaczone dla Ciebie.

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

Oprogramowanie pośredniczące z czterema argumentami, (err, req, res, next), to obsługa błędów. Express liczy argumenty: przy trzech jest to zwykłe oprogramowanie pośredniczące, które pomija błędy. Gdy trasa zgłasza wyjątek, Express pomija zwykłe oprogramowanie pośredniczące i wywołuje obsługę błędów, przekazując jej błąd. Dodaj ją na końcu, za obsługą błędu 404. Zapisz szczegóły w logu dla siebie; klientowi wyślij krótką wiadomość.

throw new Error('text') zatrzymuje trasę i przekazuje błąd do Express, podobnie jak wywołanie next(err). Zwróć kod 400, gdy użytkownik wysłał coś nieprawidłowego i może to poprawić. Zgłoś wyjątek, gdy wina leży po stronie serwera.

challenge icon

Wyzwanie

Łatwy

Kontynuuj pracę nad Recipe Box. Możesz zignorować osobne pliki practice-errors/; teraz przycisk „Uruchom” uruchamia plik server.js projektu.

Kucharze chcą przeskalować przepis, na przykład podwoić liczbę porcji na przyjęcie. Przepisu zapisanego bez liczby porcji nie da się przeskalować. To błąd w danych, a nie coś, co odwiedzający może naprawić, więc obsługuje go procedura obsługi błędów, która wysyła przejściową odpowiedź JSON o statusie 500.

  1. W pliku server.js, powyżej procedury obsługi błędu 404, dodaj trasę dla /api/recipes/:id/scale. Odczytuje ona współczynnik z ciągu zapytania, tak jak w /api/recipes/1/scale?by=2.
  2. Jeśli nie ma przepisu o tym identyfikatorze, odpowiedz statusem 404 i treścią {"error": "No such recipe"}.
  3. Zamień by na liczbę. Jeśli nie jest liczbą albo wynosi 0 lub mniej, odpowiedz statusem 400 i treścią {"error": "Scale by a positive number"}. Number('lots') daje NaN, a Number.isNaN pozwala to wykryć.
  4. Jeśli przepis nie ma servings, rzuć nowy błąd Error z komunikatem Recipe 5 has no servings, zastępując 5 identyfikatorem tego przepisu.
  5. W przeciwnym razie odpowiedz JSON-em zawierającym tylko tytuł i przeskalowaną liczbę porcji. Przepis 1, Tomato Soup, ma 4 porcje, więc /api/recipes/1/scale?by=2 wysyła {"title": "Tomato Soup", "servings": 8}. Nie dodawaj żadnych innych pól.
  6. Na końcu oprogramowania pośredniczącego w pliku, za procedurą obsługi błędu 404, dodaj procedurę obsługi błędów: app.use, którego funkcja przyjmuje cztery argumenty, (err, req, res, next). Wyświetla ona błąd za pomocą console.error i odpowiada statusem 500 oraz treścią {"error": "Something went wrong"}.

Wypróbuj: Naciśnij „Uruchom” i wyślij /api/recipes/1/scale?by=2. Powinien pojawić się wynik {"title":"Tomato Soup","servings":8}. Wyślij /api/recipes/2/scale?by=lots: status 400 i treść {"error":"Scale by a positive number"}. Wyślij /api/recipes/99/scale?by=2: status 404. Teraz wybierz POST i wyślij {"title": "Toast"} do /api/recipes. Powstanie przepis 5 bez liczby porcji. Wyślij /api/recipes/5/scale?by=2: status 500 i treść {"error":"Something went wrong"}, a w Konsoli pojawi się błąd Recipe 5 has no servings.

Spróbuj swoich sił

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') wysyła zwykły tekst, więc każdy tytuł pojawia się w osobnym wierszu.
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 iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Podstawy Express

Poćwicz samodzielnie: Edytor online Express