alltools.one
API
2025-06-30
9 min
alltools.one Team
JSONAPIRESTDesign PatternsBackend

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(不用 blogPostsblog_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 状态码

状态码含义使用场景
200OK成功的 GET、PUT
201Created成功的 POST
204No Content成功的 DELETE
400Bad Request验证错误
401Unauthorized缺少/无效认证
403Forbidden认证有效但权限不足
404Not Found资源不存在
409Conflict资源重复、版本冲突
422Unprocessable语义无效
429Too Many Requests超出速率限制
500Internal 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 删除字段。

相关资源

Published on 2025-06-30
JSON API Design Patterns: Building Better REST APIs | alltools.one