读取请求体
本课属于 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,并且不保存任何内容。
挑战
简单访客可以添加自己的食谱。程序会在 POST 请求的请求体中以 JSON 格式发送新食谱,你的 API 会保存它。
- 在
server.js中,将express.json()和其他app.use语句一起添加到路由上方,这样 JSON 请求体就会出现在req.body中。 - 为发送到
/api/recipes的 POST 请求添加路由。它会从请求体中读取title和tags,例如{"title": "Miso Soup", "tags": ["vegan", "soup"]}。 - 用下一个数字 ID 创建新食谱:现有 4 个食谱的 ID 是 1 到 4,因此第一个新食谱的 ID 是 5。保留收到的标题和标签。如果没有收到
tags,就使用空列表[]。 - 将食谱添加到
recipes数组中,这样GET /api/recipes就会列出它。然后以状态码 201 响应,并将新食谱作为 JSON 返回:{"id": 5, "title": "Miso Soup", "tags": ["vegan", "soup"]} - 如果请求体中没有
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');
});
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
Express 基础中的所有课
自己练习: Express 在线编辑器