Menu
CoddyTech

Thứ tự rất quan trọng

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 14 trên 23.

Express chạy middleware và các route theo thứ tự bạn thêm chúng vào code, và một route trả lời sẽ kết thúc yêu cầu tại đó.

app.get('/bikes', (req, res) => {
    res.json(bikes);
});

app.use((req, res, next) => {
    res.set('Cache-Control', 'no-store');
    next();
});

res.set(name, value) thêm một tiêu đề vào phản hồi, và phải chạy hàm này trước khi gửi phản hồi. Ở đây, GET /bikes trả lời trước khi middleware được chạy, nên phản hồi của nó không bao giờ có tiêu đề này: middleware chỉ nhận được những yêu cầu mà không có route nào phía trên trả lời. Middleware cần tác động đến mọi phản hồi phải nằm phía trên các route và phía trên express.static.

challenge icon

Thử thách

Dễ

Mỗi phản hồi đều có một mã id, để khi khách truy cập báo sự cố, bạn có thể tìm yêu cầu của họ trong nhật ký. Mọi phản hồi đều cần có mã này: trang, dữ liệu API và tệp do public gửi.

  1. Trong server.js, nhập randomUUID từ node:crypto. Mỗi lần gọi hàm này sẽ trả về một mã id ngẫu nhiên mới, chẳng hạn 3b241101-e2bb-4255-8caf-4136c566a962.
  2. Thêm một middleware để đặt tiêu đề phản hồi có tên X-Request-Id, chứa một randomUUID() mới cho mỗi yêu cầu, rồi gọi next().
  3. Đặt middleware này sao cho mọi yêu cầu đều đi qua nó, kể cả các tệp mà express.static gửi. Phản hồi chỉ có tiêu đề này nếu middleware chạy trước khi có thứ gì đó gửi phản hồi.

Thử ngay: Nhấn Chạy rồi gửi /api/recipes hai lần. Trong thẻ Headers, X-Request-Id có giá trị khác nhau mỗi lần. Gửi /style.css và /: mỗi phản hồi cũng có X-Request-Id.

Tự làm thử

import { escapeHtml } from './html.js';
import express from 'express';
import { recipes } from './data.js';

const app = express();

app.use((req, res, next) => {
    console.log(`${req.method} ${req.url}`);
    next();
});

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 túy, 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.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');
});

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

Tự luyện tập: Playground Express