Menu
CoddyTech

Xử lý lỗi

Thuộc phần Kiến thức cơ bản về Express trong hành trình Node.js của Coddy. Bài học 18 trên 23.

Khi một route gặp lỗi và ném ra ngoại lệ, Express sẽ trả về trang lỗi riêng, không phải JSON và hiển thị những chi tiết dành cho bạn.

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

Middleware có bốn đối số (err, req, res, next) là bộ xử lý lỗi. Express đếm số đối số: nếu có ba đối số thì đó là middleware thông thường, và lỗi sẽ bỏ qua middleware đó. Khi một route ném ra ngoại lệ, Express bỏ qua middleware thông thường và gọi bộ xử lý lỗi cùng với lỗi đó. Thêm middleware này ở cuối, sau bộ xử lý 404. Ghi nhật ký chi tiết để bạn xem, còn gửi cho client một thông báo ngắn.

throw new Error('text') dừng route và chuyển lỗi cho Express xử lý; gọi next(err) cũng có tác dụng tương tự. Trả về mã 400 khi khách truy cập gửi thông tin sai và có thể tự sửa. Ném ra ngoại lệ khi lỗi nằm ở máy chủ.

challenge icon

Thử thách

Dễ

Tiếp tục với Recipe Box. Bạn có thể bỏ qua các tệp riêng trong practice-errors/; nút Chạy giờ đây khởi động máy chủ của dự án trong server.js.

Đầu bếp muốn tăng khẩu phần của một công thức, chẳng hạn như tăng gấp đôi số khẩu phần cho một bữa tiệc. Không thể tăng khẩu phần của công thức không có thông tin số khẩu phần. Đây là lỗi trong dữ liệu của bạn, không phải vấn đề mà khách truy cập có thể khắc phục, vì vậy yêu cầu sẽ chuyển đến trình xử lý lỗi để gửi phản hồi JSON 500 rõ ràng.

  1. Trong server.js, phía trên trình xử lý 404, hãy thêm một route cho /api/recipes/:id/scale. Route này đọc hệ số từ chuỗi truy vấn, như trong /api/recipes/1/scale?by=2.
  2. Khi không có công thức nào mang id đó, hãy phản hồi với trạng thái 404 và {"error": "No such recipe"}.
  3. Chuyển by thành một số. Nếu giá trị này không phải là số hoặc bằng 0 hay nhỏ hơn 0, hãy phản hồi với trạng thái 400 và {"error": "Scale by a positive number"}. Number('lots') là NaN, và Number.isNaN giúp phát hiện giá trị đó.
  4. Khi công thức không có servings, hãy ném một Error mới với thông báo là Recipe 5 has no servings, thay số 5 bằng id của công thức đó.
  5. Nếu không, hãy phản hồi bằng JSON chỉ chứa tên công thức và số khẩu phần đã tăng. Công thức 1, Tomato Soup, có 4 khẩu phần, vì vậy /api/recipes/1/scale?by=2 sẽ gửi {"title": "Tomato Soup", "servings": 8}. Không thêm trường nào khác.
  6. Ở cuối phần middleware của tệp, sau trình xử lý 404, hãy thêm một trình xử lý lỗi: một app.use có hàm nhận bốn đối số, (err, req, res, next). Hàm này in lỗi ra bằng console.error và phản hồi với trạng thái 500 cùng {"error": "Something went wrong"}.

Thử ngay: Nhấn Chạy và gửi /api/recipes/1/scale?by=2. Bạn sẽ thấy {"title":"Tomato Soup","servings":8}. Gửi /api/recipes/2/scale?by=lots: trạng thái 400 và {"error":"Scale by a positive number"}. Gửi /api/recipes/99/scale?by=2: trạng thái 404. Bây giờ, chọn POST và gửi {"title": "Toast"} đến /api/recipes. Món này trở thành công thức 5, không có thông tin số khẩu phần. Gửi /api/recipes/5/scale?by=2: trạng thái 500 và {"error":"Something went wrong"}, còn Console hiển thị lỗi Recipe 5 has no servings.

Tự làm thử

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') gửi văn bản thuần, nên mỗi tiêu đề hiển thị trên một dòng riêng.
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 iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Kiến thức cơ bản về Express

Tự luyện tập: Playground Express