JSON API 设计模式:构建更好的 REST API
设计良好的 API 使用起来令人愉快。设计糟糕的 API 则带来 Bug、挫败感和技术债务。本指南涵盖了经过实战检验的 JSON REST API 设计模式,帮助你构建一致、可发现且可维护的 API。
资源命名
资源是名词,不是动词。集合使用复数名词,通过 ID 获取单个资源:
GET /api/users # 列出用户
POST /api/users # 创建用户
GET /api/users/123 # 获取用户 123
PUT /api/users/123 # 更新用户 123
DELETE /api/users/123 # 删除用户 123
命名规范:
- 使用小写加连字符:
/api/blog-posts(不用blogPosts或blog_posts) - 嵌套相关资源:
/api/users/123/orders - 嵌套限制为 2 层:
/api/users/123/orders/456(不要更深) - 避免 URL 中的动词:
/api/users/123/activate对于不映射到 CRUD 的操作是可接受的
响应信封
将响应包装在一致的信封中:
{
"data": {
"id": "123",
"type": "user",
"attributes": {
"name": "Alice",
"email": "alice@example.com",
"createdAt": "2024-01-15T10:30:00Z"
}
},
"meta": {
"requestId": "req_abc123"
}
}
集合响应:
{
"data": [
{ "id": "123", "name": "Alice" },
{ "id": "456", "name": "Bob" }
],
"meta": {
"total": 142,
"page": 1,
"perPage": 20
}
}
使用我们的 JSON 验证器 验证你的 API 响应以确保它们符合模式。
分页
三种常见方法:
基于偏移量(最简单)
GET /api/users?page=2&per_page=20
{
"data": [...],
"meta": {
"page": 2,
"perPage": 20,
"total": 142,
"totalPages": 8
}
}
优点:简单,支持跳转到任意页面。缺点:并发插入/删除时结果不一致。
基于游标(最可靠)
GET /api/users?cursor=eyJpZCI6MTIzfQ&limit=20
{
"data": [...],
"meta": {
"hasNext": true,
"nextCursor": "eyJpZCI6MTQzfQ"
}
}
优点:并发变更时保持一致,大数据集性能好。缺点:无法跳转到任意页面。
基于键集(性能最好)
GET /api/users?after_id=123&limit=20
使用最后一项的 ID(或其他排序字段)获取下一页。类似基于游标但参数透明。
建议:实时数据流和大数据集使用基于游标的分页。需要页面跳转的管理后台使用基于偏移量的分页。
过滤和排序
过滤
GET /api/users?status=active&role=admin
GET /api/users?created_after=2024-01-01
GET /api/users?search=alice
对于复杂过滤,考虑使用专用的查询参数:
GET /api/users?filter[status]=active&filter[role]=admin
排序
GET /api/users?sort=name # 升序
GET /api/users?sort=-created_at # 降序(前缀加 -)
GET /api/users?sort=-created_at,name # 多字段
错误处理
一致的错误响应对 API 可用性至关重要:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{
"field": "email",
"message": "Must be a valid email address",
"value": "not-an-email"
},
{
"field": "age",
"message": "Must be between 0 and 150",
"value": -5
}
]
},
"meta": {
"requestId": "req_abc123"
}
}
推荐使用的 HTTP 状态码:
| 状态码 | 含义 | 使用场景 |
|---|---|---|
| 200 | OK | 成功的 GET、PUT |
| 201 | Created | 成功的 POST |
| 204 | No Content | 成功的 DELETE |
| 400 | Bad Request | 验证错误 |
| 401 | Unauthorized | 缺少/无效认证 |
| 403 | Forbidden | 认证有效但权限不足 |
| 404 | Not Found | 资源不存在 |
| 409 | Conflict | 资源重复、版本冲突 |
| 422 | Unprocessable | 语义无效 |
| 429 | Too Many Requests | 超出速率限制 |
| 500 | Internal Error | 意外的服务器错误 |
版本管理
三种方法:
URL 路径(推荐)
GET /api/v1/users
GET /api/v2/users
优点:明确、路由简单、测试方便。
基于请求头
GET /api/users
Accept: application/vnd.myapi.v2+json
优点:URL 干净。缺点:测试困难、不易发现。
查询参数
GET /api/users?version=2
优点:测试方便。缺点:可选参数语义。
建议:URL 路径版本管理是最实用的选择。它明确、可缓存,且与每个 HTTP 工具兼容。
日期和时间处理
始终使用 UTC 的 ISO 8601 格式:
{
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T14:22:33Z",
"expiresAt": "2024-12-31T23:59:59Z"
}
永远不要在 API 响应中使用 Unix 时间戳——它们模糊不清(秒还是毫秒)且不可人类可读。关于时间戳处理的更多内容,请参阅我们的 Unix 时间戳指南。
速率限制
在响应头中传达速率限制:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 67
X-RateLimit-Reset: 1705312800
Retry-After: 30
超出限制时返回 429 Too Many Requests,附带清晰的错误信息和 Retry-After 头。
HATEOAS(可选但强大)
包含相关资源和操作的链接:
{
"data": {
"id": "123",
"name": "Alice",
"links": {
"self": "/api/users/123",
"orders": "/api/users/123/orders",
"avatar": "/api/users/123/avatar"
}
}
}
这使你的 API 具备自文档化能力并减少客户端 URL 构建。
常见问题
我应该使用 JSON:API 规范还是自己设计格式?
JSON:API 规范(jsonapi.org)提供了全面的标准,但对于简单 API 可能过于冗长。对于大多数项目,遵循本指南中的模式设计更简单的自定义格式更实用。如果你需要自动客户端库和严格标准,则使用 JSON:API。
如何处理部分更新(PATCH vs PUT)?
使用 PUT 进行完整资源替换(客户端发送所有字段)。使用 PATCH 进行部分更新(客户端只发送变更的字段)。PATCH 配合 JSON Merge Patch(RFC 7396)是最简单的方法:发送一个只包含要更新字段的 JSON 对象,用 null 删除字段。
相关资源
- JSON 格式化工具 — 格式化 API 响应以提高可读性
- JSON Schema 验证指南 — 使用 JSON Schema 验证 API 载荷
- JWT 令牌详解 — 使用 JSON Web Token 保护你的 API