第 2 章:REST API 设计规范 REST(Representational State Transfer)是目前最主流的 Web API 设计风格。理解 REST 是理解 Express 和 FastAPI 如何构建 API 的基础。
2.1 什么是 REST? REST 是 Roy Fielding 在 2000 年的博士论文中提出的一种 软件架构风格 ,不是标准、不是协议、不是框架。
它定义了一组约束,满足这些约束的 API 被称为 RESTful API。
REST 与 RPC 的对比 RPC(远程过程调用)风格:
POST /getUser → 获取用户
POST /createUser → 创建用户
POST /deleteUser → 删除用户
❌ 动词在 URL 中,HTTP 方法只有 POST
REST 风格:
GET /users/123 → 获取用户
POST /users → 创建用户
DELETE /users/123 → 删除用户
✅ 动词在 HTTP 方法中,URL 只表示资源
2.2 REST 的六大约束 1. 客户端-服务器架构(Client-Server) 客户端(浏览器/APP) 服务器(API)
│ │
│ HTTP 请求 │
│──────────────────────→│
│ │ 业务逻辑 + 数据存储
│ JSON 响应 │
│←──────────────────────│
│ │
客户端和服务器独立演进 服务器不关心客户端是什么(Web/App/CLI) 客户端不关心服务器用什么技术 2. 无状态(Stateless) 每个请求必须包含所有必要信息,服务器不保存客户端状态。
// ✅ 无状态:每次请求都带认证信息
GET / api / users / 123
Headers : {
Authorization : Bearer eyJhbGciOiJIUzI1NiIs ...
}
// ❌ 有状态:依赖服务器端的 session
GET / api / users / 123
Headers : {
Cookie : session_id = abc123 // 服务器需要查 session 表
}
无状态的好处: - 服务器可以水平扩展(任意服务器处理任意请求) - 不需要 session 共享机制 - 故障恢复简单
3. 可缓存(Cacheable) HTTP 响应必须标明是否可缓存
GET /api/products
Response:
Cache-Control: max-age=3600 ← 可缓存 1 小时
ETag: "abc123" ← 用于条件请求
GET /api/products
If-None-Match: "abc123" ← 客户端带 ETag
Response: 304 Not Modified ← 内容未变,不返回 body
这是 REST 最核心的约束,包含四个子约束:
4.1 资源标识(Resource Identification) 每个资源有唯一的 URI:
/users/123
/posts/456/comments/789
/orders/2026/05/06/abc
4.2 资源表示(Resource Representation) 同一个资源可以有多种表示格式:
GET /api/users/123
Accept: application/json → 返回 JSON
Accept: application/xml → 返回 XML
Accept: text/html → 返回 HTML
4.3 自描述消息(Self-descriptive Messages) 每个消息包含如何处理它的所有信息:
HTTP/1.1 200 OK
Content-Type: application/json ← 告诉客户端如何解析
Content-Length: 256 ← 告诉客户端消息大小
Cache-Control: no-cache ← 告诉客户端不要缓存
Link: </api/users/124>; rel="next" ← 告诉客户端下一页在哪
4.4 超媒体驱动(HATEOAS) // 响应中包含相关操作的链接
{
"id" : 123 ,
"name" : "Alice" ,
"email" : "alice@example.com" ,
"_links" : {
"self" : { "href" : "/api/users/123" },
"orders" : { "href" : "/api/users/123/orders" },
"update" : { "href" : "/api/users/123" , "method" : "PUT" },
"delete" : { "href" : "/api/users/123" , "method" : "DELETE" }
}
}
5. 分层系统(Layered System) 客户端 → 负载均衡 → 缓存层 → API 服务器 → 数据库
Nginx Redis Express PostgreSQL
每一层独立,不知道下层的实现 客户端不知道中间有代理/缓存 6. 按需编码(Code on Demand,可选) 服务器可以返回可执行代码(如 JavaScript)
GET /api/chart
Response: application/javascript
→ 返回一段 JS 代码,客户端执行后渲染图表
实际中很少使用。
2.3 HTTP 方法的语义 方法 语义 幂等 安全 示例 GET 获取资源 ✅ ✅ GET /users/123 POST 创建资源 ❌ ❌ POST /users PUT 全量更新 ✅ ❌ PUT /users/123 PATCH 部分更新 ❌ ❌ PATCH /users/123 DELETE 删除资源 ✅ ❌ DELETE /users/123
幂等性(Idempotency) 幂等 = 执行一次和执行多次的结果相同。
// PUT 是幂等的
PUT / users / 123 { name : "Alice" } → 第 1 次 : 更新为 Alice
PUT / users / 123 { name : "Alice" } → 第 2 次 : 还是 Alice ( 结果不变 )
// POST 不是幂等的
POST / users { name : "Alice" } → 第 1 次 : 创建用户 ID = 1
POST / users { name : "Alice" } → 第 2 次 : 创建用户 ID = 2 ( 结果不同 !)
// DELETE 是幂等的
DELETE / users / 123 → 第 1 次 : 删除成功 204
DELETE / users / 123 → 第 2 次 : 已删除 404 ( 资源不存在 , 但操作效果相同 )
PUT vs PATCH // 原始资源:{ id: 1, name: "Alice", email: "alice@old.com", age: 25 }
// PUT:全量替换(未提供的字段会丢失)
PUT / users / 1
Body : { "name" : "Bob" }
// 结果:{ id: 1, name: "Bob" } ← email 和 age 丢了!
// PATCH:只更新提供的字段
PATCH / users / 1
Body : { "name" : "Bob" }
// 结果:{ id: 1, name: "Bob", email: "alice@old.com", age: 25 } ← 其他字段保留
2.4 资源设计原则 1. 使用名词(复数)表示资源 // ✅ 好
GET / users // 获取用户列表
GET / users / 123 // 获取单个用户
POST / users // 创建用户
PUT / users / 123 // 更新用户
DELETE / users / 123 // 删除用户
// ❌ 差(动词在 URL 中)
GET / getAllUsers
POST / createUser
GET / getUserById
2. 嵌套资源表示关系 // 用户和文章的关系
GET / users / 123 / posts // 获取用户 123 的所有文章
GET / users / 123 / posts / 456 // 获取用户 123 的文章 456
POST / users / 123 / posts // 为用户 123 创建文章
DELETE / users / 123 / posts / 456 // 删除用户 123 的文章 456
// 文章和评论的关系
GET / posts / 456 / comments // 获取文章 456 的所有评论
POST / posts / 456 / comments // 为文章 456 添加评论
3. 过滤、排序、分页 // 过滤
GET / products ? category = electronics & price_min = 100 & price_max = 500
// 排序
GET / products ? sort =- created_at // 按创建时间降序
GET / products ? sort = price , name // 按价格升序,再按名称排序
// 分页
GET / products ? page = 2 & limit = 20 // 第 2 页,每页 20 条
GET / products ? offset = 40 & limit = 20 // 偏移 40,取 20 条
// 字段选择
GET / users ? fields = id , name , email // 只返回指定字段
// 搜索
GET / products ? q = laptop & search_in = name , description
4. 版本控制 // 方式 1:URL 版本(推荐)
GET / api / v1 / users
GET / api / v2 / users
// 方式 2:请求头版本
GET / api / users
Headers : { Accept : application / vnd . myapi . v1 + json }
// 方式 3:查询参数版本
GET / api / users ? version = 1
2.5 状态码规范 2xx 成功 状态码 含义 使用场景 200 OK GET、PUT、PATCH 成功 201 Created POST 创建资源成功 204 No Content DELETE 成功,无返回体
4xx 客户端错误 状态码 含义 使用场景 400 Bad Request 请求参数错误 401 Unauthorized 未认证 403 Forbidden 已认证但无权限 404 Not Found 资源不存在 409 Conflict 资源冲突(如重复创建) 422 Unprocessable Entity 语义错误(验证失败) 429 Too Many Requests 请求频率超限
5xx 服务器错误 状态码 含义 使用场景 500 Internal Server Error 未知服务器错误 502 Bad Gateway 上游服务错误 503 Service Unavailable 服务不可用(维护中)
错误响应格式 // 统一错误响应格式
{
"error" : {
"code" : "VALIDATION_ERROR" ,
"message" : "请求参数验证失败" ,
"details" : [
{
"field" : "email" ,
"message" : "邮箱格式不正确"
},
{
"field" : "age" ,
"message" : "年龄必须在 0-150 之间"
}
],
"request_id" : "req_abc123xyz"
}
}
2.6 实际案例:设计一个博客 API # 用户管理
POST /api/v1/users # 注册
GET /api/v1/users/me # 获取当前用户信息
PUT /api/v1/users/me # 更新当前用户信息
DELETE /api/v1/users/me # 注销账号
# 认证
POST /api/v1/auth/login # 登录
POST /api/v1/auth/logout # 登出
POST /api/v1/auth/refresh # 刷新 Token
# 文章管理
GET /api/v1/posts # 文章列表(支持过滤/分页)
GET /api/v1/posts/:id # 文章详情
POST /api/v1/posts # 创建文章
PUT /api/v1/posts/:id # 全量更新文章
PATCH /api/v1/posts/:id # 部分更新文章
DELETE /api/v1/posts/:id # 删除文章
# 评论
GET /api/v1/posts/:id/comments # 获取文章评论
POST /api/v1/posts/:id/comments # 发表评论
DELETE /api/v1/comments/:id # 删除评论
# 分类/标签
GET /api/v1/categories # 分类列表
GET /api/v1/tags # 标签列表
GET /api/v1/posts?tag=javascript # 按标签筛选
2.7 REST 的局限性 问题 说明 替代方案 过度获取 GET /users/123 返回所有字段,但只需要 name GraphQL、字段选择 多次请求 获取用户 + 文章 + 评论需要 3 次请求 GraphQL、批量接口 版本膨胀 v1、v2、v3...维护成本高 向后兼容设计 文件上传 REST 不适合处理大文件 专用上传服务、预签名 URL
2.8 总结 核心概念 一句话 无状态 每个请求自包含,不依赖服务器 session 统一接口 URL 表示资源,HTTP 方法表示操作 幂等性 PUT/DELETE 执行多次效果相同,POST 不同 状态码 2xx 成功、4xx 客户端错误、5xx 服务器错误 版本控制 URL 版本 /api/v1/ 最常用
REST 不是唯一选择,但是最广泛接受的选择。 理解 REST 后,再学 GraphQL/gRPC 会更容易。
上一章:Node.js 原理详解 | 下一章:Express.js 框架