REST API 设计规范
设计一个优秀的REST API需要遵循一系列规范和最佳实践。本章将详细介绍URL设计、HTTP方法使用、状态码规范以及错误处理等内容。
URL设计原则
URL是资源的唯一标识符,良好的URL设计是REST API的基础。
使用名词表示资源
URL应该表示资源,而不是操作。资源使用名词表示,操作通过HTTP方法体现。
正确示例:
GET /users 获取用户列表
POST /users 创建新用户
GET /users/123 获取ID为123的用户
PUT /users/123 更新ID为123的用户
DELETE /users/123 删除ID为123的用户
错误示例:
GET /getUsers ❌ URL中包含动词
POST /createUser ❌ 操作应该在HTTP方法中体现
DELETE /deleteUser/123 ❌ 同上
使用复数形式
资源名称应该使用复数形式,这样语义更清晰,也便于扩展。
正确示例:
/users 用户集合
/users/123 单个用户
/articles 文章集合
/articles/456 单篇文章
错误示例:
/user ❌ 单数形式
/article/456 ❌ 单数形式
表达资源层级关系
当资源之间存在层级关系时,可以通过URL路径表达这种关系。
/users/123/orders 用户123的所有订单
/users/123/orders/456 用户123的订单456
/articles/456/comments 文章456的所有评论
/articles/456/comments/789 文章456的评论789
层级关系的设计原则:
- 层级不宜过深,一般不超过3层
- 如果子资源可以独立存在,考虑使用单独的URL
- 通过查询参数过滤,而不是增加层级
使用连字符提高可读性
当资源名称包含多个单词时,使用连字符(-)连接,不要使用下划线或驼峰命名。
正确示例:
/blog-posts
/user-profiles
/order-items
错误示例:
/blogPosts ❌ 驼峰命名
/blog_posts ❌ 下划线
/blogposts ❌ 单词连在一起
使用小写字母
URL应该全部使用小写字母,因为URL是大小写敏感的,使用小写可以避免混淆。
正确示例:
/users
/blog-posts
/api-docs
错误示例:
/Users ❌ 大写字母
/BlogPosts ❌ 大写字母
避免尾部斜杠
URL末尾不应该有斜杠,这会导致歧义和重复。
正确示例:
/users
/articles/123
错误示例:
/users/ ❌ 尾部斜杠
/articles/123/ ❌ 尾部斜杠
使用查询参数过滤和分页
对于列表资源的过滤、排序、分页,应该使用查询参数而不是URL路径。
过滤:
GET /articles?status=published&author=123
排序:
GET /articles?sort=createdAt&order=desc
分页:
GET /articles?page=2&limit=20
搜索:
GET /articles?keyword=REST
组合使用:
GET /articles?status=published&sort=createdAt&order=desc&page=1&limit=10
版本控制
API版本应该放在URL中,通常使用v前缀。
/api/v1/users
/api/v2/users
版本号的位置选择:
- 放在域名后:
https://api.example.com/v1/users - 放在路径前:
https://example.com/api/v1/users
动作资源的设计
有些操作难以用标准HTTP方法表达,可以使用"动作资源"模式。
POST /articles/123/publish 发布文章
POST /orders/456/cancel 取消订单
POST /users/123/follow 关注用户
这些URL表示的是一种"动作资源",POST请求创建这个动作资源,从而触发相应的业务逻辑。
HTTP方法规范
HTTP方法定义了对资源的操作类型,正确使用HTTP方法是REST API设计的关键。
常用HTTP方法
| 方法 | 用途 | 是否幂等 | 是否安全 |
|---|---|---|---|
| GET | 获取资源 | 是 | 是 |
| POST | 创建资源 | 否 | 否 |
| PUT | 完整更新资源 | 是 | 否 |
| PATCH | 部分更新资源 | 否 | 否 |
| DELETE | 删除资源 | 是 | 否 |
幂等性和安全性
安全性:安全方法不会修改服务器上的资源状态。GET、HEAD、OPTIONS是安全方法。
幂等性:幂等方法执行一次和执行多次的效果相同。GET、PUT、DELETE、HEAD、OPTIONS是幂等方法。
理解这两个概念对于设计可靠的API非常重要:
// GET是安全和幂等的
GET /users/123
// 无论执行多少次,服务器状态不变,返回结果相同
// PUT是幂等的
PUT /users/123
{ "name": "张三" }
// 执行一次和执行多次,用户123的名字都是"张三"
// POST不是幂等的
POST /orders
{ "userId": 123, "amount": 100 }
// 每次执行都会创建一个新订单!
GET方法
GET方法用于获取资源,是最常用的HTTP方法。
获取单个资源:
GET /users/123 HTTP/1.1
Host: api.example.com
Accept: application/json
获取资源列表:
GET /users?page=1&limit=20 HTTP/1.1
Host: api.example.com
Accept: application/json
GET请求的注意事项:
- 不应该在请求体中发送数据
- 可以被缓存
- 参数在URL中传递
- 不应该修改服务器状态
POST方法
POST方法用于创建新资源。
POST /users HTTP/1.1
Host: api.example.com
Content-Type: application/json
{
"name": "张三",
"email": "[email protected]"
}
响应应该包含:
HTTP/1.1 201 Created
Content-Type: application/json
Location: /users/124
{
"id": 124,
"name": "张三",
"email": "[email protected]",
"createdAt": "2024-10-21T10:30:00Z"
}
POST也常用于执行复杂操作:
POST /orders/456/cancel HTTP/1.1
Host: api.example.com
Content-Type: application/json
{
"reason": "用户主动取消"
}
PUT方法
PUT方法用于完整更新资源,需要提供资源的所有字段。
PUT /users/123 HTTP/1.1
Host: api.example.com
Content-Type: application/json
{
"name": "李四",
"email": "[email protected]",
"age": 25
}
PUT的特点:
- 必须提供完整资源数据
- 如果资源不存在,通常会创建新资源
- 是幂等操作
PATCH方法
PATCH方法用于部分更新资源,只需要提供要修改的字段。
PATCH /users/123 HTTP/1.1
Host: api.example.com
Content-Type: application/json
{
"email": "[email protected]"
}
PATCH的特点:
- 只修改提供的字段
- 其他字段保持不变
- 不是幂等操作(取决于实现)
DELETE方法
DELETE方法用于删除资源。
DELETE /users/123 HTTP/1.1
Host: api.example.com
响应可以是:
HTTP/1.1 204 No Content
或者返回被删除的资源:
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 123,
"deleted": true
}
HTTP状态码规范
HTTP状态码是API响应的重要组成部分,正确使用状态码可以帮助客户端理解请求结果。
状态码分类
| 类别 | 含义 |
|---|---|
| 1xx | 信息性响应 |
| 2xx | 成功 |
| 3xx | 重定向 |
| 4xx | 客户端错误 |
| 5xx | 服务器错误 |
常用状态码详解
2xx 成功状态码
200 OK:请求成功,用于GET、PUT、PATCH、DELETE。
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 123,
"name": "张三"
}
201 Created:资源创建成功,用于POST。
HTTP/1.1 201 Created
Location: /users/124
Content-Type: application/json
{
"id": 124,
"name": "张三"
}
204 No Content:请求成功,无返回内容。常用于DELETE或PUT。
HTTP/1.1 204 No Content
202 Accepted:请求已接受,但处理尚未完成。用于异步操作。
HTTP/1.1 202 Accepted
Content-Type: application/json
{
"taskId": "abc123",
"status": "processing",
"estimatedTime": "5 minutes"
}
3xx 重定向状态码
301 Moved Permanently:资源已永久移动到新位置。
HTTP/1.1 301 Moved Permanently
Location: /api/v2/users/123
304 Not Modified:资源未修改,使用缓存版本。配合ETag或Last-Modified使用。
HTTP/1.1 304 Not Modified
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
4xx 客户端错误状态码
400 Bad Request:请求格式错误或参数无效。
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"error": "Invalid request",
"message": "Email format is invalid",
"field": "email"
}
401 Unauthorized:未认证,需要登录。
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="api"
{
"error": "Unauthorized",
"message": "Authentication required"
}
403 Forbidden:已认证但无权限访问。
HTTP/1.1 403 Forbidden
Content-Type: application/json
{
"error": "Forbidden",
"message": "You do not have permission to access this resource"
}
404 Not Found:资源不存在。
HTTP/1.1 404 Not Found
Content-Type: application/json
{
"error": "Not Found",
"message": "User with id 999 not found"
}
405 Method Not Allowed:请求方法不允许。
HTTP/1.1 405 Method Not Allowed
Allow: GET, POST, PUT
Content-Type: application/json
{
"error": "Method Not Allowed",
"message": "DELETE method is not supported for this resource"
}
409 Conflict:请求与服务器状态冲突。
HTTP/1.1 409 Conflict
Content-Type: application/json
{
"error": "Conflict",
"message": "Email already exists",
"field": "email"
}
422 Unprocessable Entity:请求格式正确但语义错误。
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
{
"error": "Validation Failed",
"errors": [
{
"field": "password",
"message": "Password must be at least 8 characters"
},
{
"field": "age",
"message": "Age must be a positive number"
}
]
}
429 Too Many Requests:请求过于频繁,触发限流。
HTTP/1.1 429 Too Many Requests
Retry-After: 60
Content-Type: application/json
{
"error": "Too Many Requests",
"message": "Rate limit exceeded. Please retry after 60 seconds"
}
5xx 服务器错误状态码
500 Internal Server Error:服务器内部错误。
HTTP/1.1 500 Internal Server Error
Content-Type: application/json
{
"error": "Internal Server Error",
"message": "An unexpected error occurred",
"requestId": "req-abc123"
}
502 Bad Gateway:网关或代理服务器收到无效响应。
503 Service Unavailable:服务暂时不可用。
HTTP/1.1 503 Service Unavailable
Retry-After: 300
Content-Type: application/json
{
"error": "Service Unavailable",
"message": "Service is temporarily unavailable for maintenance"
}
504 Gateway Timeout:网关或代理服务器等待上游响应超时。
错误处理规范
良好的错误处理可以帮助开发者快速定位问题,提高开发效率。
错误响应格式
统一的错误响应格式应该包含以下信息:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "请求参数验证失败",
"details": [
{
"field": "email",
"message": "邮箱格式不正确"
},
{
"field": "password",
"message": "密码长度至少8位"
}
],
"requestId": "req-abc123",
"timestamp": "2024-10-21T10:30:00Z",
"documentation": "https://api.example.com/docs/errors/VALIDATION_ERROR"
}
}
错误码设计
错误码应该具有描述性,便于开发者理解和搜索:
VALIDATION_ERROR 参数验证错误
AUTH_REQUIRED 需要认证
AUTH_EXPIRED 认证已过期
PERMISSION_DENIED 权限不足
RESOURCE_NOT_FOUND 资源不存在
RESOURCE_CONFLICT 资源冲突
RATE_LIMITED 请求频率超限
INTERNAL_ERROR 内部错误
分页设计
列表接口应该支持分页,避免返回过多数据。
请求参数:
GET /articles?page=1&limit=20
响应格式:
{
"data": [
{ "id": 1, "title": "文章1" },
{ "id": 2, "title": "文章2" }
],
"pagination": {
"page": 1,
"limit": 20,
"total": 100,
"totalPages": 5
},
"links": {
"first": "/articles?page=1&limit=20",
"prev": null,
"next": "/articles?page=2&limit=20",
"last": "/articles?page=5&limit=20"
}
}
过滤、排序、字段选择
过滤:
GET /articles?status=published&author=123
排序:
GET /articles?sort=createdAt:desc,title:asc
字段选择:
GET /articles?fields=id,title,author.name
请求和响应格式
请求格式
Content-Type:指定请求体的格式。
POST /users HTTP/1.1
Content-Type: application/json
{
"name": "张三",
"email": "[email protected]"
}
Accept:指定期望的响应格式。
GET /users/123 HTTP/1.1
Accept: application/json
响应格式
响应应该包含适当的头部信息:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Cache-Control: max-age=3600
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
X-Request-Id: req-abc123
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
{
"id": 123,
"name": "张三"
}
总结
设计优秀的REST API需要遵循一系列规范:
- URL设计:使用名词、复数形式、小写字母、连字符
- HTTP方法:正确使用GET、POST、PUT、PATCH、DELETE
- 状态码:使用合适的状态码表达请求结果
- 错误处理:提供清晰、一致的错误信息
- 分页和过滤:支持分页、过滤、排序等查询功能
遵循这些规范,可以设计出易用、一致、可维护的REST API。