后端工程师之间流传一句话:「写 API 容易,写让人想用的 API 难。」两个团队对接,最耗时间的往往不是逻辑,而是反复确认「这个字段叫什么」「报错长什么样」「删除了返回什么」。REST 不是强制标准,而是一套约定俗成的风格。接口设计得「合群」,对接成本就会低一大截。

一、把一切看成「资源」

REST 的核心是资源(resource)。用户、订单、文章,都是名词。URL 应该描述「是什么」,而不是「做什么」。

反例:/getUser?id=1/deleteOrder。正例:/users/1/orders/123。动作交给 HTTP 方法表达:GET 取、POST 建、PUT/PATCH 改、DELETE 删。这样看一眼 URL 和方法,就知道意图,不用翻文档猜动词。

资源用复数更稳:/users 表示集合,/users/1 表示其中一个。保持统一,别一会儿 user 一会儿 users

二、用 HTTP 方法表达语义

GET 应当是安全的——它只读取,不改状态。很多事故来自「用 GET 做删除」被爬虫或预加载点了一下,数据没了。POST 用于新建,PUT 通常表示整体替换,PATCH 表示局部更新。DELETE 顾名思议。

幂等性值得记一下:GET、PUT、DELETE 多次调用结果一致(删一次和删十次,资源都是没了);POST 不保证(重复提交可能建出两条)。这直接影响前端要不要做「防重复提交」。

三、状态码是接口的「语气」

客户端靠状态码判断成败,而不是去解析返回体里的 success: true。常见映射:

  • 200 OK:成功返回数据
  • 201 Created:创建成功(常带 Location 指向新资源)
  • 400 Bad Request:请求本身有错(参数不对)
  • 401 Unauthorized:没登录
  • 403 Forbidden:登录了但没权限
  • 404 Not Found:资源不存在
  • 500 Internal Server Error:服务器炸了

坑在于「一律返回 200,错误塞在 body 里」。这种做法让网关、重试逻辑、监控都失去依据,前端也得每个请求额外判 body,很累。该用什么码就用什么码。

四、返回结构要稳定

同一类接口,成功和失败的返回形状尽量一致。比如统一 { data, error, message },前端用一套解析逻辑通吃。别这个接口返回数组、那个返回 { list: [...] }、另一个又包一层 { result: { items } }——对接方会疯。

时间、ID、空值这些细节也要约定:时间用 ISO 8601 字符串还是时间戳?空集合返回 [] 还是 null?文档里写清楚,能少一堆边界 bug。

五、分页、过滤与排序

列表接口几乎都要分页。惯用做法:/users?page=2&size=20,返回里带上总数 total 和当前页,前端才好画页码。过滤用查询参数:/users?role=admin&status=active。排序:?sort=-createdAt(负号表示降序)。

这里有个容易被忽视的点:查询参数里如果含特殊字符(空格、中文、&、=),必须正确编码,否则服务端收不到预期值。调试时把原始 URL 丢进 URL 编码工具 看编码前后差异,能快速定位「明明传了却读不到」的问题。

六、版本管理别等到火烧眉毛

接口一旦有人用,就不能随便改字段名。常见的做法是 URL 加版本:/v1/users,或者在 Header 里带版本。新需求破坏旧结构时,开 /v2,旧版本留一段时间并通知下线。

认证也顺带提一句:令牌(token)在传输前常需编码或拼接。比如 Basic Auth 把 user:pass 做一次 Base64 再塞进 Header。理解这一步,用 Base64 编解码 自己编一下、再解回来,能确认你传给后端的东西长什么样,排查鉴权失败很实用。

七、文档与可调试性

再好的设计,没有文档也等于零。接口字段、示例请求响应、错误码清单,最好能自动生成(很多框架支持从代码导出)。前端对接前,用 JSON 格式化工具 把一坨压缩的响应展开,字段层级看得清清楚楚,写适配代码快很多。

八、一个设计清单

每次定接口前过一遍:URL 是名词复数吗?方法语义对吗?状态码用对了吗?返回结构统一吗?分页参数约定了吗?版本预留了吗?错误有清晰 code 吗?这八条做到,对接方基本不会来找你吵架。

小结:REST 的价值不在「严格」,而在「共识」。资源用名词、动作交方法、成败看状态码、结构要统一、参数需编码、版本早预留。接口自己会说话,团队就少吵架。