版本管理
本课属于 Coddy Node.js 学习旅程的“REST API”单元。第 22 课,共 25 课。
更改答案的结构会影响所有已经在使用它的客户端。在路径中加入版本号,就能让新旧客户端并行使用:今天用 /api/v1/orders,结构更改时用 /api/v2/orders。路径迁移时,旧路径会重定向到新路径:
GET /api/orders/5 → 308 Location: /api/v1/orders/5
POST /api/orders → 308 Location: /api/v1/orders301 和 302 会让客户端把 POST 重复为 GET 请求,从而丢失请求正文。308 表示“在新地址重复相同的请求”,包括方法和请求正文。在挂载的中间件内部,req.url 不再包含挂载路径,而 req.originalUrl 会保留完整路径:
// 在处理 GET /api/orders/5?full=1 的 app.use('/api/orders', ...) 中
req.url // '/5?full=1'
req.originalUrl // '/api/orders/5?full=1'挑战
简单你的 API 会以旧客户端意想不到的方式变化,因此要在路径中加入版本号。今天的 API 成为第 1 版,地址为 /api/v1/tasks,旧地址会将客户端重定向到这里。
- 在
server.js中,将 tasks 路由器挂载到/api/v1/tasks,而不是/api/tasks。 - 同样在
server.js中,在/api/tasks上挂载一个只负责重定向的中间件。它会以状态码 308 将请求重定向到/api/v1下的相同地址,并保留路径的其余部分和查询字符串。在已挂载的中间件中,req.originalUrl保存着完整的旧地址。例如:GET /api/tasks/2重定向到/api/v1/tasks/2。POST /api/tasks重定向到/api/v1/tasks。GET /api/tasks?done=false&sort=title重定向到/api/v1/tasks?done=false&sort=title。
使用 308,而不是 301:收到 308 后,客户端会再次发送 POST 请求,并保留请求体。
- 在
routes/tasks.js中,POST 路由将Location设置为新地址,例如,任务 4 的地址是/api/v1/tasks/4。
试试看: GET /api/tasks/2 会返回 308,Headers 选项卡会显示 Location: /api/v1/tasks/2。请求栏会显示重定向本身,不会跟随重定向。向 /api/tasks 发送 POST 请求,内容为 { "title": "Order new chairs" }:你会得到 308 和 Location: /api/v1/tasks,并且不会创建任务。向 /api/v1/tasks 发送相同的请求体:会得到 201 和 Location: /api/v1/tasks/4。GET /api/v1/tasks/2 会返回任务 2。
亲手试试
import express from 'express';
import { sendError } from './errors.js';
import taskRoutes from './routes/tasks.js';
const BOARD_ORIGIN = 'https://board.example.com';
const app = express();
// 允许 BOARD_ORIGIN 上的页面通过浏览器调用此 API。
app.use((req, res, next) => {
res.set('Access-Control-Allow-Origin', BOARD_ORIGIN);
if (req.method === 'OPTIONS') {
res.set('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS');
res.set('Access-Control-Allow-Headers', 'Content-Type');
return res.status(204).end();
}
next();
});
app.use(express.json());
app.use(express.static('public'));
app.use('/api/tasks', taskRoutes);
// 路由中的错误最终会在这里处理。请求体不是有效的 JSON,属于
// 客户端的错误(400);其他错误都属于服务器端(500)。
app.use((err, req, res, next) => {
if (err.type === 'entity.parse.failed') {
return sendError(res, 400, 'invalid_json', 'The body is not valid JSON');
}
console.error(err);
sendError(res, 500, 'server_error', 'Something went wrong');
});
app.listen(3000, () => {
console.log('Task tracker API on port 3000');
});
REST API中的所有课
自己练习: Express 在线编辑器