写在前面
作为一名后端开发,你一定写过API。但你确定你写的是RESTful API,而不是「REST-like」或者「我高兴就好」式API?
今天咱们就来聊聊RESTful API设计那些事儿,顺便看看有哪些坑是咱们可以避免的。
什么是RESTful?先把这个词说人话
REST(Representational State Transfer)这个概念是Roy Fielding在他2000年的博士论文里提出的。说人话就是:用HTTP协议的设计哲学来构建API。
听起来简单?但我见过太多「披着REST外衣的RPC」了。
RPC(Remote Procedure Call)就是远程过程调用,说白了就是「我想调用远端的某个函数」。而REST强调的是「资源」和「状态」。
记住一个核心原则:URL表示资源,HTTP方法表示操作。搞明白这个,你就已经赢了一半。
HTTP方法:你真的会用吗?
这个问题看起来简单,但我见过太多奇葩用法了。
GET:读取资源
GET请求应该是幂等的(多次执行结果一致),而且不应该有副作用。什么是副作用?就是你调用一次和调用两次,对服务器状态的影响是一样的。
✅ 正确:GET /users/123
✅ 正确:GET /users?status=active
❌ 错误:GET /users/123/delete
POST:创建资源
POST用于创建资源,通常放在集合(collection)URL上:
✅ 正确:POST /users (创建新用户)
✅ 正确:POST /users/123/orders (为用户123创建订单)
PUT:完整更新
PUT是幂等的,表示「你要么给我完整的资源,要么就一边凉快去」:
✅ 正确:PUT /users/123 (更新用户123的全部信息)
❌ 错误:用PUT做部分更新(这不是PUT的职责)
PATCH:部分更新
PATCH才是做部分更新的:
✅ 正确:PATCH /users/123 (只更新用户123的邮箱)
// 请求体:{ "email": "newemail@example.com" }
DELETE:删除资源
DELETE也是幂等的(删除一个不存在的东西,结果还是不存在):
✅ 正确:DELETE /users/123
✅ 正确:DELETE /users/123/orders (删除用户123的所有订单)
URL设计:别把URL当成函数名
见过太多这种URL了:
❌ 错误示例:
GET /getUserById?id=123
POST /createNewUser
PUT /updateUserInfo
DELETE /deleteUserById?id=123
这不就是把函数名放在URL里吗?请问你的HTTP方法意义何在?
正确的姿势:
✅ 正确示例:
GET /users/123
POST /users
PUT /users/123
DELETE /users/123
复数还是单数?这是个问题
业界普遍推荐使用复数形式:
✅ GET /users (获取用户列表)
✅ GET /users/123 (获取指定用户)
// 不是说单数不能用,而是复数更一致
// 毕竟列表是复数,单个资源也是复数形式的一种
嵌套资源怎么玩?
✅ GET /users/123/orders (用户123的订单)
✅ GET /users/123/orders/456 (用户123的456号订单)
✅ POST /users/123/orders (为用户123创建订单)
但要注意,嵌套别太深,一般建议不超过两层:
❌ 错误:/users/123/orders/456/items/789
✅ 正确:/orders/456/items/789 或者直接 /order-items/789
状态码:别再只会用200和500了
HTTP状态码是API和客户端沟通的重要语言,但太多人只认识200、404、500三个了。
2xx:成功系列
- 200 OK - 最通用的成功
- 201 Created - 资源创建成功
- 204 No Content - 成功但没返回内容(常用于DELETE)
4xx:客户端错误系列
- 400 Bad Request - 请求参数有问题
- 401 Unauthorized - 没认证(或者认证失败)
- 403 Forbidden - 没权限(认证了但没权限)
- 404 Not Found - 资源不存在
- 409 Conflict - 资源冲突(比如重复创建)
- 422 Unprocessable Entity - 请求格式对,但语义错(比如验证失败)
- 429 Too Many Requests - 请求太频繁了
5xx:服务器错误系列
- 500 Internal Server Error - 服务器抽风了
- 502 Bad Gateway - 网关错误
- 503 Service Unavailable - 服务不可用
我的建议是:尽量精确地返回状态码。你返回403和401,客户端的感知是完全不同的。
版本控制:API也要版本管理
API一旦发布,就像泼出去的水。所以版本控制很重要。
URL版本(最常见)
✅ 推荐:
GET /v1/users
GET /v2/users
❌ 不推荐(太土):
GET /users?version=1
Header版本(更优雅但复杂)
Accept: application/vnd.myapp.v1+json
Accept: application/vnd.myapp.v2+json
我的建议是:初创项目用URL版本就够了,简单粗暴有效。等真到了需要兼容多个版本的体量,再考虑Header版本。
错误处理:返回一个像样的错误
见过太多这样的错误返回了:
❌ 错误示例:
{ "error": "出错了" }
{ "message": "操作失败" }
这让人怎么调试?怎么知道哪里出问题了?
正确的错误格式应该是这样的:
✅ 正确示例:
{
"error": {
"code": "USER_NOT_FOUND",
"message": "用户不存在",
"details": {
"user_id": "123",
"reason": "该用户已被删除"
}
}
}
或者更详细的:
{
"status": 404,
"error": "Not Found",
"message": "用户ID 123 不存在",
"timestamp": "2024-01-15T10:30:00Z"
}
分页和过滤:别一下返回全部数据
「爱一个人可以,但不能惯着她」——写API也是同理,你不能惯着前端同事一下把所有数据都拉下来。
分页
GET /users?page=2&per_page=20
Response:
{
"data": [...],
"pagination": {
"page": 2,
"per_page": 20,
"total": 1000,
"total_pages": 50
}
}
过滤
GET /users?status=active&city=beijing
GET /users?role=admin&created_at_gt=2024-01-01
排序
GET /users?sort=created_at&order=desc
GET /users?sort=name,created_at&order=asc,desc
总结:好API的标准
最后咱们来总结一下,一个好的RESTful API应该具备哪些特质:
- 资源导向 - URL表示资源,不是动作
- 方法正确 - GET读、POST创、PUT改、DELETE删
- 状态码精准 - 用对HTTP状态码,别偷懒
- 错误友好 - 返回有用的错误信息
- 版本可控 - 做好版本管理,别让前端抓狂
- 响应节制 - 适度返回数据,别惯着
记住一句话:API也是代码,也要优雅。你写代码讲究个可读性,API设计也一样。
好了,今天的分享就到这里。如果你也有什么API设计的奇葩经历,欢迎评论区聊聊。咱们下期见!