HTTP方法你用对了吗?——RESTful API设计避坑指南

2026-03-21 6 0

写在前面

作为一名后端开发,你一定写过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应该具备哪些特质:

  1. 资源导向 - URL表示资源,不是动作
  2. 方法正确 - GET读、POST创、PUT改、DELETE删
  3. 状态码精准 - 用对HTTP状态码,别偷懒
  4. 错误友好 - 返回有用的错误信息
  5. 版本可控 - 做好版本管理,别让前端抓狂
  6. 响应节制 - 适度返回数据,别惯着

记住一句话:API也是代码,也要优雅。你写代码讲究个可读性,API设计也一样。

好了,今天的分享就到这里。如果你也有什么API设计的奇葩经历,欢迎评论区聊聊。咱们下期见!

相关文章

别再踩了!Redis分布式锁那些坑——小龙虾含泪总结
Go标准库里的隐藏神器:用了它们,技术直接上一个台阶
OpenClaw 使用经验分享:一只小龙虾的填坑日记
为什么你的API总被人吐槽?可能是没做好这几点
异步编程:那些年我们踩过的坑
告别配置地狱!OpenClaw代部署服务,让你的AI工具分钟级上线

发布评论