好的 API 设计能让开发者用得爽、少踩坑、效率高。差的 API 设计则会让对接的人骂娘,文档写了一堆还是看不懂。REST 是目前最主流的 API 设计风格,但很多人所谓的 REST API 其实只是"HTTP 接口"——用了 URL 和 JSON,但完全没抓住 REST 的精髓。本文总结了 REST API 设计的最佳实践,从命名规范到错误处理,从版本控制到安全认证,帮你设计出专业优雅的 API。
1. 资源命名:REST 的核心
REST 的核心思想是资源。URL 应该代表资源(名词),而不是动作(动词)。HTTP 方法(GET/POST/PUT/DELETE)才是动作。
// ❌ 不好的设计(动词 + 动作)
GET /getUsers
POST /createUser
POST /updateUser?id=123
GET /deleteUser?id=123
// ✅ 好的设计(名词复数 + HTTP方法)
GET /users // 获取用户列表
GET /users/123 // 获取单个用户
POST /users // 创建用户
PUT /users/123 // 全量更新用户
PATCH /users/123 // 部分更新用户
DELETE /users/123 // 删除用户
命名原则
- 使用名词复数:/users 而非 /user,保持一致性
- 使用小写字母和短横线:/user-profiles 而非 /UserProfiles 或 /user_profiles
- 层级关系用路径:GET /users/123/orders(获取用户 123 的所有订单)
- 过滤参数用 query string:GET /users?status=active&page=1&pageSize=20
- 避免嵌套过深:一般不超过 2 层,/users/123/orders/456/items 就有点深了
2. HTTP 方法与状态码的正确使用
REST API 应该充分利用 HTTP 协议本身的语义,而不是把所有请求都变成 POST + 自定义错误码。
方法语义
GET // 读取资源,安全且幂等
POST // 创建资源,非幂等
PUT // 全量更新资源,幂等
PATCH // 部分更新资源,非幂等(但通常当幂等用)
DELETE // 删除资源,幂等
HEAD // 获取资源元信息(和GET一样但不返回body)
OPTIONS // 获取资源支持的方法和能力
什么是幂等?执行一次和执行 N 次效果相同。比如 PUT /users/123,不管调用多少次,用户 123 的最终状态都是你设置的那样。而 POST /users 调用多次会创建多个用户,所以非幂等。
状态码使用规范
200 OK // GET/PUT/PATCH 成功
201 Created // POST 创建成功(响应体包含新资源)
204 No Content // DELETE 成功(无响应体)
400 Bad Request // 请求参数错误
401 Unauthorized // 未认证
403 Forbidden // 已认证但无权限
404 Not Found // 资源不存在
409 Conflict // 资源冲突(如重复创建、乐观锁失败)
422 Unprocessable Entity // 语义错误(格式对但内容错)
429 Too Many Requests // 限流
500 Internal Server Error // 服务端错误
3. 统一响应格式
统一的响应格式让前端对接更简单,减少心智负担。推荐格式:
// 成功响应
{
"code": 0,
"message": "success",
"data": {
"id": 123,
"name": "张三"
}
}
// 列表响应
{
"code": 0,
"message": "success",
"data": {
"items": [ ... ],
"total": 100,
"page": 1,
"pageSize": 20,
"totalPages": 5
}
}
// 错误响应
{
"code": 40001,
"message": "用户名已存在",
"details": [
{ "field": "username", "message": "该用户名已被注册" }
],
"requestId": "req_abc123"
}
code 字段建议区分 HTTP 状态码和业务错误码。HTTP 状态码表示协议层面的结果,业务 code 表示具体的业务错误(如 40001=用户名已存在、40002=密码错误)。
4. 分页、过滤与排序
列表接口一定要支持分页。常见的分页方式有两种:
// 偏移量分页(简单但深分页性能差)
GET /users?page=1&pageSize=20
GET /users?offset=0&limit=20
// 游标分页(性能更好,适合大数据量)
GET /users?limit=20&cursor=eyJpZCI6MTAwfQ
过滤和排序的设计:
// 多条件过滤
GET /users?status=active&role=admin
// 范围查询
GET /orders?createdAt[gte]=2024-01-01&createdAt[lte]=2024-01-31
// 排序(- 表示降序)
GET /users?sort=-createdAt,name
// 按创建时间降序,同时间按姓名升序
// 字段选择(只返回需要的字段,减少传输)
GET /users?fields=id,name,email
5. 版本控制
API 一旦发布,修改就要非常谨慎。破坏性变更必须通过新版本发布。常见的版本控制方案有三种:
// 1. URL 路径版本(最直观,推荐)
GET /api/v1/users
GET /api/v2/users
// 2. 查询参数版本
GET /api/users?version=2
// 3. Header 版本(更 RESTful 但不够直观)
GET /api/users
Accept: application/vnd.example.v2+json
URL 版本虽然不够"纯 REST",但最易理解和调试,也是业界最常用的方案。Stripe、GitHub 等大厂都使用这种方式。
6. 错误处理与调试支持
好的错误信息应该告诉开发者三件事:出了什么错、为什么出错、怎么修复。
- 错误信息要具体:不要只说"参数错误",要说"email 格式不正确"
- 返回 requestId:方便开发者找你查日志时快速定位
- 字段级错误:表单验证错误时,指出具体哪个字段有问题
- 提供错误码文档:每个业务错误码都应有对应的文档说明
开发环境可以额外返回 stack trace 方便调试,但生产环境必须关闭,避免泄露服务器信息。
7. 安全与认证
认证方式
// Bearer Token(JWT 最常用)
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
// API Key(适合服务端集成)
X-API-Key: sk_live_abc123
// Basic Auth(简单但不安全,仅限内部或测试)
Authorization: Basic dXNlcjpwYXNz
安全最佳实践
- 全程 HTTPS:所有 API 必须通过 HTTPS 传输
- 输入校验:永远不要信任客户端传过来的任何数据
- 限流:防止暴力破解和爬虫,返回 429 + Retry-After
- 防止注入:SQL 注入、NoSQL 注入、命令注入都要防范
- CORS 配置:限制允许的域名,不要用
* - 速率限制:按 IP 或用户 ID 限制请求频率
- 审计日志:记录关键操作,便于事后追溯
8. 文档与测试
再完美的 API,没有好文档也会让人痛苦。API 文档应该包含:
- 每个接口的功能说明、HTTP 方法和 URL
- 请求参数说明(路径参数、query 参数、请求体)
- 响应字段说明,最好有完整示例
- 错误码说明
- 认证方式说明
- 可交互的 API 调试工具(Try it out)
推荐工具:OpenAPI/Swagger 是 API 文档的事实标准;Postman 适合手动测试;Hoppscotch 是开源的轻量级 API 测试工具。自动生成文档的方案:代码注释生成(JSDoc + swagger-jsdoc)、代码优先(TypeScript 类型生成 OpenAPI)、设计优先(先写 OpenAPI 再生成代码)。
9. HTTP 缓存设计
合理设计 API 的缓存策略,可以大幅减少服务端压力,提升客户端响应速度。REST API 应该充分利用 HTTP 缓存机制。
缓存头的选择
// 强缓存:浏览器直接使用本地缓存,不发请求
Cache-Control: max-age=3600 // 缓存 1 小时
Cache-Control: public, max-age=86400 // 公开缓存,1 天
// 协商缓存:发请求验证资源是否变化
ETag: "abc123def456" // 资源版本标识
Last-Modified: Wed, 21 Aug 2024 10:00:00 GMT
// 不缓存
Cache-Control: no-cache // 每次都验证
Cache-Control: no-store // 完全不缓存
API 缓存策略建议
- GET 接口:数据变化不频繁的用强缓存,实时性要求高的用协商缓存
- POST/PUT/DELETE:不要缓存,这些操作会改变资源状态
- 静态数据:如省市区列表、字典数据,可以设置较长缓存时间
- 用户数据:用 no-cache 或短缓存,配合 ETag 验证
10. 幂等性设计
网络是不可靠的,请求可能超时、可能重复发送。幂等性保证同一个请求执行一次和执行多次效果相同,是分布式系统的重要设计原则。
// 使用 Idempotency-Key 保证幂等
// 客户端每次请求生成一个唯一 key
POST /payments
Idempotency-Key: uuid-v4-key
Content-Type: application/json
{
"amount": 100,
"orderId": "ORD123"
}
// 服务端逻辑:
// 1. 根据 idempotency key 查找是否已有记录
// 2. 有记录:直接返回之前的结果,不重复处理
// 3. 没记录:正常处理并保存结果和 key 的关联
支付、下单、转账这类操作,幂等性至关重要。Stripe 等支付平台都要求使用 Idempotency-Key。除了自定义 Header,也可以用业务字段实现幂等,比如订单号、流水号等唯一业务标识。
11. 分页与大数据量处理
当数据量很大时,普通的偏移量分页会有性能问题——offset 越大,查询越慢。游标分页是更好的选择。
// 偏移量分页(简单但深分页慢)
GET /articles?page=100&pageSize=20
// 后端:SELECT * FROM articles ORDER BY id LIMIT 20 OFFSET 1980;
// 游标分页(性能稳定)
GET /articles?limit=20&cursor=eyJpZCI6MjAwfQ
// 后端:SELECT * FROM articles WHERE id > 200 ORDER BY id LIMIT 20;
// 响应中返回下一页游标
{
"items": [...],
"nextCursor": "eyJpZCI6MjIwfQ==", // 编码后的游标
"hasMore": true
}
游标分页的缺点是只能顺序翻页,不能跳转到指定页。但对于无限滚动(Infinite Scroll)的场景,游标分页是完美选择。大多数移动端 App 的 feed 流都采用这种方式。
12. 国际化与本地化
如果你的 API 服务全球用户,需要考虑多语言、多时区、多货币等问题。
// 通过 Header 指定语言和时区
Accept-Language: zh-CN, en;q=0.9
X-Timezone: Asia/Shanghai
X-Currency: CNY
// 或者通过 query 参数
GET /api/users?lang=zh-CN&timezone=Asia/Shanghai
错误信息、日期格式、数字格式都应该根据用户的语言和地区来本地化。返回的时间统一使用 ISO 8601 + UTC 格式,客户端根据用户时区转换显示。货币一定要带单位和代码(如 "amount": 100, "currency": "CNY"),否则数字本身没有意义。
13. 速率限制(Rate Limiting)
限流是保护 API 不被滥用的重要手段。常见的限流算法有:
- 固定窗口:最简单,但边界处可能有双倍流量
- 滑动窗口:更平滑,但实现稍复杂
- 令牌桶:允许一定程度的突发流量
- 漏桶:严格控制请求速率,平滑输出
// 标准的限流响应头
X-RateLimit-Limit: 100 // 时间窗口内的请求上限
X-RateLimit-Remaining: 42 // 剩余请求数
X-RateLimit-Reset: 1692672000 // 限流重置的时间戳(Unix 时间)
Retry-After: 60 // 触发限流后,多少秒后可以重试
// 触发限流时返回 429 状态码
HTTP/1.1 429 Too Many Requests
Retry-After: 60
Content-Type: application/json
{
"error": "rate_limit_exceeded",
"message": "请求过于频繁,请稍后再试",
"retryAfter": 60
}
14. API 设计检查清单
设计完 API 后,对照这个清单检查一下:
- URL 使用名词复数,没有动词?
- HTTP 方法语义正确(GET 读取、POST 创建、PUT 全量更新、PATCH 部分更新、DELETE 删除)?
- 状态码使用恰当(2xx/3xx/4xx/5xx 各归其位)?
- 响应格式统一(code/message/data 结构)?
- 列表接口有分页、过滤、排序?
- 错误信息具体明确,有错误码和 requestId?
- 有版本控制策略?
- 认证方式安全(HTTPS + Bearer Token)?
- 有限流机制?
- 有完整的 API 文档?
- GET 请求是幂等的?
- 写入操作考虑了幂等性?
- 时间使用 ISO 8601 格式?
- 有缓存策略(ETag/Cache-Control)?
- CORS 配置安全合理?
好的 API 设计需要时间和经验的积累,但只要遵循这些基本原则,就能设计出专业、优雅、好用的 API。记住:API 是给人用的,不是给机器用的。让调用你的 API 的开发者感到舒服,就是最好的设计。
总结
API 设计是一门平衡的艺术——要符合 REST 规范,也要兼顾实际开发效率;要灵活强大,也要简单易用。记住几个核心原则:以资源为中心、充分利用 HTTP 语义、保持一致性、为开发者着想。好的 API 应该是"自解释"的——开发者看一眼 URL 就知道是什么资源,看 HTTP 方法就知道做什么操作,看状态码就知道结果。