API(Application Programming Interface,应用程序编程接口)是现代Web开发的基础,无论是前后端分离、移动端开发,还是第三方集成,都离不开API。REST(Representational State Transfer,表述性状态转移)是目前最流行的API设计风格,它基于HTTP协议,简单、灵活、易于扩展。
但是,很多开发者设计的RESTful API却混乱不堪:URL命名不规范、HTTP方法用错、状态码乱用、请求响应格式不统一、没有版本控制、没有文档……这样的API不仅难用,还难以维护,给前后端协作和第三方集成都带来了很多麻烦。
2016年,RESTful API已经成为Web开发的标准,前后端分离、移动端开发、微服务架构都离不开API。掌握RESTful API设计的最佳实践,是每个后端开发者的必备技能。今天就来详细讲解RESTful API设计的最佳实践,从URL命名到版本控制,帮助你设计出优雅、易用、可维护的API。
一、REST基础概念
1. 什么是REST
REST是一种软件架构风格,由Roy Fielding在2000年的博士论文中提出。REST的核心思想是:将所有事物都抽象为资源(Resource),每个资源有一个唯一的URI(统一资源标识符),通过HTTP方法(GET、POST、PUT、DELETE等)对资源进行操作。
REST的六个约束:
- 客户端-服务器分离:客户端和服务器职责分离,通过接口通信
- 无状态:每个请求都是独立的,服务器不保存客户端状态
- 可缓存:响应可以被缓存,提高性能
- 统一接口:使用统一的HTTP方法和状态码
- 分层系统:客户端不知道是否直接连接到最终服务器
- 按需代码(可选):服务器可以向客户端传输可执行代码
2. RESTful API的特点
- 基于HTTP协议,使用HTTP方法表示操作
- 使用URI表示资源
- 使用JSON(或XML)作为数据交换格式
- 使用HTTP状态码表示请求结果
- 无状态,每个请求独立
- 可缓存,提高性能
二、URL设计最佳实践
URL是API的入口,好的URL设计应该简洁、清晰、有意义、易于理解。
1. 使用名词,不用动词
REST的核心是资源,资源是名词,所以URL中应该使用名词,而不是动词。HTTP方法已经表示了操作(GET=查询,POST=创建,PUT=更新,DELETE=删除),不需要在URL中再加动词。
❌ 错误:
GET /getUsers # 获取用户列表
GET /getUser?id=123 # 获取单个用户
POST /createUser # 创建用户
POST /updateUser # 更新用户
POST /deleteUser # 删除用户
✅ 正确:
GET /users # 获取用户列表
GET /users/123 # 获取单个用户
POST /users # 创建用户
PUT /users/123 # 更新用户(全量更新)
PATCH /users/123 # 更新用户(部分更新)
DELETE /users/123 # 删除用户2. 使用复数名词
资源通常是集合,所以使用复数名词更符合直觉。
✅ 推荐:
/users # 用户集合
/users/123 # 集合中的单个用户
/posts # 文章集合
/posts/456 # 单篇文章
/comments # 评论集合虽然也有人使用单数(/user/123),但是复数更常见、更符合REST惯例。
3. 层级关系用斜杠
如果资源之间有层级关系(从属关系),用斜杠表示。
✅ 推荐:
/users/123/posts # 用户123的所有文章
/users/123/posts/456 # 用户123的第456篇文章
/posts/456/comments # 文章456的所有评论
/posts/456/comments/789 # 文章456的第789条评论
/categories/10/posts # 分类10下的所有文章但是,层级不要太深,一般不超过2-3层,否则URL会变得复杂难记。如果层级太深,可以考虑用查询参数代替。
4. 用连字符(-)分隔单词,不用下划线(_)
URL中多个单词之间用连字符(-)分隔,不用下划线(_)。这是因为连字符在大多数字体中更清晰可读,而且搜索引擎对连字符的处理更好。
✅ 推荐:
/user-profiles # 用户资料
/post-categories # 文章分类
/order-items # 订单项
❌ 不推荐:
/user_profiles # 下划线
/userProfiles # 驼峰(URL不区分大小写,容易混淆)5. 全部小写
URL路径全部小写,因为URL不区分大小写(域名部分不区分,路径部分取决于服务器,但惯例是小写)。全部小写可以避免混淆。
✅ 推荐:
/users/123/posts
❌ 不推荐:
/Users/123/Posts
/USERS/123/POSTS6. 查询参数用于过滤、排序、分页
URL路径表示资源,查询参数(?key=value)用于过滤、排序、分页等操作。
✅ 推荐:
/users?status=active # 过滤:只返回活跃用户
/users?role=admin # 过滤:只返回管理员
/users?sort=created_at&order=desc # 排序:按创建时间倒序
/users?page=2&per_page=20 # 分页:第2页,每页20条
/users?status=active&role=admin&page=1 # 组合使用7. 不要在URL中加文件扩展名
不要在URL中加.json、.xml等文件扩展名。内容格式应该通过Accept请求头来协商,而不是URL扩展名。
✅ 推荐:
/users/123 # 通过Accept: application/json指定返回JSON
❌ 不推荐:
/users/123.json # 扩展名
/users/123.xml # 扩展名三、HTTP方法最佳实践
HTTP方法表示对资源的操作,每个方法有明确的语义,不要混用。
| 方法 | 语义 | 幂等 | 安全 | 示例 |
|---|---|---|---|---|
| GET | 查询资源 | ✅ | ✅ | GET /users, GET /users/123 |
| POST | 创建资源 | ❌ | ❌ | POST /users |
| PUT | 全量更新资源 | ✅ | ❌ | PUT /users/123 |
| PATCH | 部分更新资源 | ❌ | ❌ | PATCH /users/123 |
| DELETE | 删除资源 | ✅ | ❌ | DELETE /users/123 |
- 幂等:多次执行结果相同。GET、PUT、DELETE是幂等的,POST、PATCH不是。
- 安全:不修改服务器资源。只有GET是安全的。
1. GET
用于查询资源,不应该修改服务器状态。GET请求应该是幂等的(多次调用结果相同)和安全的(不修改资源)。
GET /users # 获取用户列表
GET /users/123 # 获取单个用户
GET /users/123/posts # 获取用户的文章列表GET请求的参数应该放在URL查询参数中,不要放在请求体中(虽然HTTP规范没有禁止,但很多服务器和代理不处理GET请求体)。
2. POST
用于创建资源。POST不是幂等的,多次调用会创建多个资源。
POST /users # 创建用户
POST /users/123/posts # 为用户123创建文章
POST /posts/456/comments # 为文章456创建评论创建成功后,应该返回201 Created状态码,并在Location响应头中指向新创建的资源URI。
3. PUT
用于全量更新资源。PUT是幂等的,多次调用结果相同。PUT要求客户端发送完整的资源数据,服务器用客户端发送的数据完全替换原有资源。
PUT /users/123 # 全量更新用户123
# 请求体包含完整的用户数据:name, email, age, address等所有字段注意:如果客户端只发送了部分字段,PUT会把未发送的字段设为null或默认值(全量替换)。如果只想更新部分字段,应该用PATCH。
4. PATCH
用于部分更新资源。PATCH不是幂等的(取决于实现)。PATCH只更新客户端发送的字段,未发送的字段保持不变。
PATCH /users/123 # 部分更新用户123
# 请求体只包含需要更新的字段:{"email": "new@example.com"}
# 其他字段(name, age等)保持不变PATCH在2016年还不是所有框架都原生支持,但是越来越流行。如果框架不支持PATCH,可以用POST模拟,或者用PUT全量更新。
5. DELETE
用于删除资源。DELETE是幂等的,多次删除同一个资源结果相同(都是删除)。
DELETE /users/123 # 删除用户123
DELETE /posts/456/comments/789 # 删除评论789删除成功后返回204 No Content(无响应体)或200 OK(带响应体说明删除结果)。
四、HTTP状态码最佳实践
HTTP状态码表示请求的结果,应该正确使用,不要所有请求都返回200 OK然后在响应体中用code字段表示状态。
1. 常用状态码
| 状态码 | 含义 | 使用场景 |
|---|---|---|
| 200 OK | 成功 | GET查询成功、PUT/PATCH更新成功、DELETE删除成功(带响应体) |
| 201 Created | 已创建 | POST创建资源成功 |
| 204 No Content | 无内容 | DELETE删除成功(无响应体)、PUT/PATCH更新成功(无响应体) |
| 301 Moved Permanently | 永久重定向 | 资源URI永久变更 |
| 302 Found | 临时重定向 | 资源URI临时变更 |
| 304 Not Modified | 未修改 | 缓存验证,资源未变化 |
| 400 Bad Request | 请求错误 | 请求参数错误、格式错误 |
| 401 Unauthorized | 未认证 | 未登录、Token无效或过期 |
| 403 Forbidden | 禁止访问 | 已登录但无权限 |
| 404 Not Found | 未找到 | 资源不存在 |
| 405 Method Not Allowed | 方法不允许 | HTTP方法不支持 |
| 409 Conflict | 冲突 | 资源冲突(如用户名已存在、版本冲突) |
| 410 Gone | 已删除 | 资源曾经存在但已永久删除 |
| 422 Unprocessable Entity | 无法处理 | 请求格式正确但语义错误(如验证失败) |
| 429 Too Many Requests | 请求过多 | 限流,请求频率超限 |
| 500 Internal Server Error | 服务器内部错误 | 服务器异常 |
| 502 Bad Gateway | 网关错误 | 网关或代理服务器错误 |
| 503 Service Unavailable | 服务不可用 | 服务器维护或过载 |
| 504 Gateway Timeout | 网关超时 | 网关或代理超时 |
2. 状态码使用原则
- 2xx:成功。200表示成功,201表示创建成功,204表示成功但无响应体。
- 3xx:重定向。301永久重定向,302临时重定向,304缓存未修改。
- 4xx:客户端错误。400参数错误,401未认证,403无权限,404不存在,422验证失败,429限流。
- 5xx:服务器错误。500服务器内部错误,503服务不可用。
3. 不要这样做
❌ 错误:所有请求都返回200,在响应体中用code表示状态
HTTP/1.1 200 OK
{
"code": 404,
"message": "用户不存在",
"data": null
}
✅ 正确:使用正确的HTTP状态码
HTTP/1.1 404 Not Found
{
"error": "not_found",
"message": "用户不存在"
}五、请求和响应格式最佳实践
1. 使用JSON作为数据格式
2016年,JSON已经成为API数据交换的标准格式。JSON简洁、易读、跨语言支持好。除非有特殊需求(如XML用于SOAP),否则都应该用JSON。
请求头:
Content-Type: application/json
Accept: application/json
请求体:
{
"name": "张三",
"email": "zhangsan@example.com",
"age": 25
}
响应体:
{
"id": 123,
"name": "张三",
"email": "zhangsan@example.com",
"age": 25,
"created_at": "2016-06-17T10:00:00Z",
"updated_at": "2016-06-17T10:00:00Z"
}2. 统一的响应格式
API的响应格式应该统一,让客户端可以用一致的方式解析。
成功响应:
{
"data": {
"id": 123,
"name": "张三",
"email": "zhangsan@example.com"
}
}列表响应(带分页):
{
"data": [
{"id": 1, "name": "张三"},
{"id": 2, "name": "李四"}
],
"meta": {
"total": 100,
"page": 1,
"per_page": 20,
"last_page": 5
}
}错误响应:
{
"error": {
"code": "validation_error",
"message": "请求参数验证失败",
"details": [
{
"field": "email",
"message": "邮箱格式不正确"
},
{
"field": "age",
"message": "年龄必须大于0"
}
]
}
}统一的响应格式让客户端可以用一致的方式处理成功和错误,提高开发效率。
3. 字段命名规范
JSON字段命名应该统一,推荐使用snake_case(下划线命名),因为这是Ruby、Python、PHP等语言的惯例,也符合数据库字段命名。也可以用camelCase(驼峰命名),这是JavaScript的惯例。关键是要统一,不要混用。
✅ 推荐(snake_case):
{
"user_name": "张三",
"created_at": "2016-06-17T10:00:00Z",
"is_active": true
}
✅ 也可以(camelCase):
{
"userName": "张三",
"createdAt": "2016-06-17T10:00:00Z",
"isActive": true
}
❌ 不要混用:
{
"user_name": "张三", // snake_case
"createdAt": "2016-06-17", // camelCase
"IsActive": true // PascalCase
}4. 时间格式
时间应该使用ISO 8601格式,这是国际标准,跨语言支持好。
✅ 推荐:
"created_at": "2016-06-17T10:00:00Z" // UTC时间
"created_at": "2016-06-17T18:00:00+08:00" // 带时区
❌ 不推荐:
"created_at": "2016-06-17 10:00:00" // 无时区,格式不标准
"created_at": 1466157600 // Unix时间戳(不直观)
"created_at": "06/17/2016" // 格式不明确5. 布尔值
布尔值应该用true/false,不要用1/0或"true"/"false"字符串。
✅ 推荐:
"is_active": true
"is_deleted": false
❌ 不推荐:
"is_active": 1
"is_active": "true"六、分页、过滤、排序最佳实践
1. 分页
列表接口必须支持分页,避免一次返回太多数据导致性能问题。
✅ 推荐:
GET /users?page=1&per_page=20 # 第1页,每页20条
GET /users?offset=0&limit=20 # 偏移量方式(适合大数据量、需要深度分页的场景)分页参数:
page:页码,从1开始perpage/pagesize/limit:每页条数,默认20,最大100offset:偏移量(offset = (page-1) * per_page)
响应中应该包含分页元数据:
{
"data": [...],
"meta": {
"total": 100, // 总条数
"page": 1, // 当前页
"per_page": 20, // 每页条数
"last_page": 5 // 总页数
}
}对于大数据量的深度分页(如page=10000),offset方式性能很差(MySQL需要扫描前面所有行)。可以用游标分页(cursor-based pagination):
GET /users?cursor=12345&limit=20
# cursor是上一页最后一条记录的id,返回id > 12345的20条记录2. 过滤
过滤参数直接放在查询参数中:
GET /users?status=active # 按状态过滤
GET /users?role=admin # 按角色过滤
GET /users?status=active&role=admin # 多条件组合
GET /posts?category_id=5 # 按分类过滤
GET /posts?tag=php # 按标签过滤
GET /posts?created_from=2016-01-01&created_to=2016-06-30 # 时间范围过滤对于复杂的过滤(如范围、模糊搜索),可以用专门的参数名:
GET /users?q=张三 # 模糊搜索
GET /users?min_age=18&max_age=60 # 年龄范围
GET /posts?date_from=2016-01-01&date_to=2016-06-30 # 日期范围3. 排序
排序参数用sort和order(或direction):
GET /users?sort=created_at&order=desc # 按创建时间倒序
GET /users?sort=name&order=asc # 按名称正序
GET /posts?sort=views&order=desc # 按浏览量倒序(热门文章)多字段排序:
GET /users?sort=status,created_at&order=asc,desc
# 先按status正序,再按created_at倒序默认排序:如果客户端没有指定排序,应该有一个合理的默认排序(如按created_at倒序,最新的在前)。
七、认证和授权最佳实践
1. 认证(Authentication)
认证是验证用户身份的过程。2016年主流的API认证方式:
- API Key:在请求头或查询参数中传递API Key,简单但安全性较低,适合服务端到服务端的API。
`` GET /api/users X-API-Key: abc123def456 ``
- OAuth 2.0:行业标准的授权框架,适合第三方应用授权。流程复杂但安全性高。
`` GET /api/users Authorization: Bearer <access_token> ``
- JWT(JSON Web Token):2016年越来越流行的无状态认证方式。Token中包含用户信息,服务器不需要存储Session,适合分布式和微服务架构。
`` GET /api/users Authorization: Bearer <jwt_token> ``
- Session/Cookie:传统的Web认证方式,有状态,适合传统Web应用,不适合纯API。
推荐:2016年,JWT是API认证的热门选择,无状态、适合分布式、跨域支持好。OAuth 2.0适合需要第三方授权的场景。
2. 授权(Authorization)
授权是验证用户是否有权限执行某个操作的过程。
- 基于角色的访问控制(RBAC):给用户分配角色(如admin、editor、user),角色有权限,用户通过角色获得权限。简单易用,适合大多数应用。
- 基于属性的访问控制(ABAC):根据用户、资源、环境的属性动态判断权限,灵活但复杂。
API中应该在服务端做权限校验,不要依赖前端隐藏按钮。每个API都应该检查当前用户是否有权限访问该资源或执行该操作。
3. 安全实践
- 使用HTTPS,所有API通信加密
- 不要在URL中传递敏感信息(如Token、密码),应该放在请求头或请求体中
- Token应该有过期时间,支持刷新
- 密码应该用bcrypt等安全算法哈希存储,不要明文存储
- 对API进行限流,防止滥用和DDoS攻击
- 对输入进行验证和过滤,防止SQL注入、XSS等攻击
- 不要在错误信息中暴露敏感信息(如数据库错误、文件路径)
八、版本控制最佳实践
API会不断演进,版本控制是必须的。没有版本控制的API,一旦修改就可能破坏现有客户端。
1. 版本控制方式
方式一:URL中加版本号(推荐)
GET /api/v1/users
GET /api/v2/users优点:简单直观,容易区分版本,方便同时维护多个版本。 缺点:URL变长,需要维护多个版本的路由。
方式二:请求头中加版本号
GET /api/users
Accept: application/vnd.myapp.v1+json优点:URL干净,符合REST原则(内容协商)。 缺点:不直观,调试困难,很多开发者不熟悉。
方式三:查询参数加版本号
GET /api/users?version=1优点:简单。 缺点:不优雅,容易被忽略,不推荐。
推荐:URL中加版本号(/api/v1/),简单直观,2016年大多数API都采用这种方式。
2. 版本控制原则
- 一旦发布某个版本,就不应该再做破坏性修改(如删除字段、修改字段类型)
- 新功能可以在新版本中添加,旧版本保持稳定
- 应该有版本废弃计划,提前通知用户旧版本何时停止支持
- 文档中应该说明每个版本的变更
- 同时维护的版本不要太多(通常2-3个),否则维护成本高
3. 什么情况下需要新版本
- 删除字段或端点
- 修改字段名或类型
- 修改API行为(如默认值、排序方式)
- 修改认证方式
- 重大架构调整
以下情况不需要新版本(向后兼容):
- 添加新字段
- 添加新端点
- 添加新的可选参数
- 优化性能
- 修复bug(不改变预期行为)
九、错误处理最佳实践
1. 统一的错误响应格式
{
"error": {
"code": "validation_error",
"message": "请求参数验证失败",
"details": [
{
"field": "email",
"message": "邮箱格式不正确"
}
]
}
}字段说明:
code:错误码,字符串,程序可以根据错误码做不同处理message:错误信息,人类可读,用于显示给用户details:详细错误信息,如字段验证错误的具体字段和原因
2. 错误码设计
错误码应该有意义、有层次,不要用数字(如10001、10002),应该用字符串(如validationerror、notfound、unauthorized)。
常见错误码:
invalid_request:请求格式错误validation_error:参数验证失败unauthorized:未认证forbidden:无权限not_found:资源不存在conflict:资源冲突rate_limited:请求频率超限internal_error:服务器内部错误
3. 错误信息原则
- 错误信息应该清晰、有意义,帮助开发者快速定位问题
- 不要暴露敏感信息(如数据库错误、堆栈跟踪、文件路径)
- 生产环境返回简洁的错误信息,开发环境可以返回详细的调试信息
- 错误信息应该本地化(根据Accept-Language返回对应语言)
十、文档最佳实践
API没有文档,就像产品没有说明书。好的API文档能大大提高开发效率,减少沟通成本。
1. 文档应该包含
- API概述(简介、基础URL、认证方式)
- 每个端点的详细说明(URL、HTTP方法、请求参数、响应格式、状态码、示例)
- 认证和授权说明
- 错误码说明
- 版本变更日志
- 常见问题(FAQ)
- 代码示例(curl、JavaScript、Python、PHP等)
2. 文档工具
2016年流行的API文档工具:
- Swagger(OpenAPI):行业标准,支持YAML/JSON定义API,自动生成交互式文档。2016年越来越流行。
- API Blueprint:用Markdown写API文档,简洁易读。
- RAML:RESTful API建模语言。
- Postman:API测试工具,也可以生成文档。
- GitBook / ReadMe:通用文档平台。
推荐:Swagger(OpenAPI),2016年已经成为API文档的事实标准,支持自动生成文档、客户端SDK、服务端框架等。
3. 文档原则
- 文档应该和代码同步更新,不要文档和实际API不一致
- 文档应该有示例,示例比文字说明更直观
- 文档应该易于搜索和导航
- 文档应该有版本,对应API的版本
- 鼓励开发者反馈文档问题,持续改进
十一、其他最佳实践
1. 限流(Rate Limiting)
对API进行限流,防止滥用和DDoS攻击。可以按IP、用户、API Key限流。
限流信息应该在响应头中返回:
X-RateLimit-Limit: 1000 # 每小时限制1000次
X-RateLimit-Remaining: 995 # 剩余995次
X-RateLimit-Reset: 1466157600 # 重置时间(Unix时间戳)超过限流返回429 Too Many Requests。
2. 缓存
对不常变化的数据进行缓存,提高性能,减轻服务器压力。可以用HTTP缓存头(Cache-Control、ETag、Last-Modified),也可以用Redis等服务端缓存。
响应头:
Cache-Control: max-age=3600 # 缓存1小时
ETag: "abc123" # 资源版本标识
Last-Modified: Wed, 17 Jun 2016 10:00:00 GMT3. CORS(跨域资源共享)
如果API需要被浏览器端的JavaScript调用(前后端分离),需要配置CORS。
响应头:
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 3600不要用Access-Control-Allow-Origin: *(允许所有来源),除非是公开API。应该指定具体的域名。
4. 请求ID
给每个请求分配一个唯一的请求ID,方便日志追踪和问题排查。
响应头:
X-Request-Id: a1b2c3d4e5f6客户端也可以发送X-Request-Id,服务端记录在日志中,出现问题时可以通过请求ID快速定位。
5. 健康检查端点
提供一个健康检查端点,用于监控和负载均衡。
GET /health
响应:200 OK {"status": "ok"}总结
RESTful API设计是一门艺术,也是一门科学。好的API设计应该简洁、清晰、有意义、易于使用、易于维护。本文从URL设计、HTTP方法、状态码、请求响应格式、分页过滤排序、认证授权、版本控制、错误处理、文档等方面,讲解了RESTful API设计的最佳实践。
核心原则总结:
- URL用名词复数,不用动词,HTTP方法表示操作
- 正确使用HTTP状态码,不要所有请求都返回200
- 使用JSON格式,统一请求响应格式
- 列表接口支持分页、过滤、排序
- 使用HTTPS和安全的认证方式(JWT/OAuth 2.0)
- API必须有版本控制,URL中加版本号(/api/v1/)
- 统一的错误处理,清晰的错误码和错误信息
- 完善的API文档,和代码同步更新
- 限流、缓存、CORS、请求ID等工程实践
- 向后兼容,非必要不做破坏性修改
API是给开发者用的,好的API能让开发者用得舒心、用得高效。希望本文的最佳实践能帮助你设计出优雅、易用、可维护的RESTful API。
最后,记住:API设计没有绝对的标准答案,关键是要统一、一致、有文档。只要你的团队达成共识,严格遵守约定,就是好的API设计。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录