Menu
CoddyTech

读取请求体

本课属于 Coddy Node.js 学习旅程的“Express 基础”单元。第 15 课,共 23 课。

POST 请求会在请求体中发送数据,除非你明确要求 Express 读取请求体,否则它不会读取。

app.use(express.json());

app.post('/api/loans', (req, res) => {
    const { bookId, member } = req.body;
    res.status(201).json({ bookId, member, due: '2026-11-08' });
});

GET 用来请求内容,POST 用来发送要创建的内容。express.json() 是一种中间件,会将每个 JSON 请求体读入 req.body。没有它,req.body 就是 undefined。创建内容的路由会返回 201,并附上它创建的内容。

保存请求体之前先检查它。缺少字段是客户端的错误,因此应返回 400,并且不保存任何内容。

challenge icon

挑战

简单

访客可以添加自己的食谱。程序会在 POST 请求的请求体中以 JSON 格式发送新食谱,你的 API 会保存它。

  1. 在 server.js 中,将 express.json() 和其他 app.use 语句一起添加到路由上方,这样 JSON 请求体就会出现在 req.body 中。
  2. 为发送到 /api/recipes 的 POST 请求添加路由。它会从请求体中读取 title 和 tags,例如 {"title": "Miso Soup", "tags": ["vegan", "soup"]}。
  3. 用下一个数字 ID 创建新食谱:现有 4 个食谱的 ID 是 1 到 4,因此第一个新食谱的 ID 是 5。保留收到的标题和标签。如果没有收到 tags,就使用空列表 []。
  4. 将食谱添加到 recipes 数组中,这样 GET /api/recipes 就会列出它。然后以状态码 201 响应,并将新食谱作为 JSON 返回:{"id": 5, "title": "Miso Soup", "tags": ["vegan", "soup"]}
  5. 如果请求体中没有 title,就不保存任何内容,并以状态码 400 和以下 JSON 响应:{"error": "A recipe needs a title"}

试试看:按“运行”。在方法选择器中选择 POST,将 /api/recipes 输入为路径,并在请求体输入框中填写 {"title": "Miso Soup", "tags": ["vegan", "soup"]}。你应该会看到状态码 201,以及包含 "id": 5 的食谱。选择 GET 并发送 /api/recipes:你会看到 5 个食谱,Miso Soup 排在最后。然后向 /api/recipes 发送 POST 请求,并填写 {"tags": ["dinner"]}:状态码为 400,且响应为 {"error":"A recipe needs a title"}。GET /api/recipes 仍会列出 5 个食谱。

亲手试试

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.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') 发送纯文本,因此每个标题各占一行。
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');
});
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

Express 基础中的所有课

自己练习: Express 在线编辑器