编程 RESTful API 设计:URL 不放动词、错误不返 200,以及名词化吵不出结果的几类接口

2026-10-04 00:04:04

RESTful API 设计:URL 不放动词、错误不返 200,以及名词化吵不出结果的几类接口

底稿:阮一峰《RESTful API 最佳实践》,另整理其评论区里几类长期没有统一答案的接口。

一、URL 设计

用「动词 + 宾语」表达操作

客户端对数据的操作指令是「动词 + 宾语」。GET /articles 里,GET 是动词,/articles 是宾语。

五种 HTTP 方法与 CRUD 的对应关系:

  • GET:读取
  • POST:新建
  • PUT:更新(全量)
  • PATCH:更新(部分)
  • DELETE:删除

HTTP 方法一律大写。

动词覆盖

有些客户端只能发 GET 和 POST。服务器要接受用 POST 模拟 PUT / PATCH / DELETE,客户端加请求头:

POST /api/Person/4 HTTP/1.1
X-HTTP-Method-Override: PUT

宾语必须是名词

URL 是动词作用的对象,应该是名词。

  • 正确:/articles
  • 错误:/getAllCars、/createNewCar、/deleteAllRedCars

用复数 URL

建议统一用复数:GET /articles/2 好于 GET /article/2。

避免多级 URL

GET /authors/12/categories/2 不利于扩展,语义也不明。除第一级外,其他维度用查询字符串:

GET /authors/12?categories=2

查询已发布的文章用 GET /articles?published=true,而不是 GET /articles/published。

二、状态码

状态码分五大类:1xx 信息、2xx 成功、3xx 重定向、4xx 客户端错误、5xx 服务器错误。API 不需要 1xx。

2xx

  • GET → 200
  • POST → 201 Created
  • PUT → 200
  • PATCH → 200
  • DELETE → 204 No Content

202 Accepted 表示请求已收到、但异步处理:

{"task": {"href": "...", "id": "2130040"}}

3xx

API 基本用不到 301 / 302 / 307,主要是 303 See Other,用于 POST / PUT / DELETE 之后的重定向,配合 Location 头:

HTTP/1.1 303 See Other
Location: /api/orders/12345

4xx

400 Bad Request、401 Unauthorized、403 Forbidden、404 Not Found、405 Method Not Allowed、410 Gone、415 Unsupported Media Type、422 Unprocessable Entity、429 Too Many Requests。

5xx

一般只要 500 Internal Server Error 和 503 Service Unavailable,不向用户暴露服务器细节。

三、服务器回应

不要返回纯文本

返回 JSON 对象,响应头带 Content-Type: application/json;请求方发 Accept: application/json。

发生错误不要返回 200

错误也返 200、把错误塞进 body:

{"status": "failure", "data": {"error": "..."}}

这等于取消了状态码。正确做法是状态码反映错误、错误详情放 body:

HTTP/1.1 400 Bad Request
Content-Type: application/json

{"error": "Invalid payload.", "detail": {"surname": "This field is required."}}

提供链接 HATEOAS

响应里给出相关链接,客户端只记一个入口就能发现其他 URL(GitHub 的 api.github.com 就是这种做法)。更好的写法是把 links 与其他属性分开:

{
  "status": "In progress",
  "links": [
    {"rel": "cancel", "method": "delete", "href": "..."},
    {"rel": "edit", "method": "put", "href": "..."}
  ]
}

四、名词化吵不出结果的场景

纯动作类接口

下单、支付、退订、启用 / 禁用、验证码校验。名词化都很难:

  • 一种主张是 POST /orders/下单、PUT /users/{id}?status=enable
  • 另一种主张是拆成资源:POST /orders、PUT /orders/{id}
  • GitHub 用的是 /datas/1/action 这种形式

没有统一结论,选一种并在团队内保持一致。

检查类接口

用户名是否已存在,可以理解为「尝试获取」:GET /users/{name},不存在就返回 404;也可以写成 /users/{name}?c=check。

批量操作

PUT /users/{id}/enable 只适合单个。批量要另设集合级接口,GitHub 用 actions 前缀:/actions/:action/resources,例如 /actions/restart/servers。

HTTP 状态码 vs 业务错误码

一派认为 HTTP 状态码表达传输 / 协议层错误,业务逻辑错误(用户名密码不匹配、余额不足)用 body 里的 code 字段约定;另一派主张所有错误都体现在 HTTP 状态码上。多数人的做法是两者并存:HTTP 状态码给通用语义,body 带业务 code。

跨域取不到非 200 状态码

错。只要基于 HTTP,前端就能拿到状态码;拿不到是没正确配置 CORS 暴露头,不是要把错误都返 200。

错误返回 200

有人觉得这样后端少判断一步。实际结果是缓存、重试、监控全部失效。

参考链接

推荐文章

程序员茄子在线接单