在这篇文章中,我正在尽我最大的努力压缩我所知道的关于什么是好的 API 的一切。您的消费者会喜欢使用的 API。所有提示都与语言无关,因此它们适用于任何框架或技术。
1. 保持一致
- 对字段、资源和参数使用相同的大小写(我更喜欢snake_case)
- 使用复数或单数资源名称(我更喜欢复数)
- /users/{id},/orders/{id}或/user/{id},/order/{id}
- 对所有端点使用相同的身份验证和授权方法
- 在 API 中使用相同的 HTTP 标头
- 例如Api-Key用于传递 API 密钥
- 根据响应类型使用相同的 HTTP 状态代码
- 例如404,当找不到资源时
- 对相同类型的操作使用相同的 HTTP 方法
- 例如DELETE删除资源时
2. 使用ISO 8601 UTC 日期
在处理日期和时间时,API 应始终返回 ISO 8601 格式的字符串。在特定时区显示日期通常是客户端应用程序关心的问题。
{ |
3. 对公共端点进行例外处理
默认情况下,每个端点都应该需要授权。大多数端点都需要调用经过身份验证的用户,因此将其设为默认值是有意义的。如果需要公开调用端点,请显式设置此端点以允许未经授权的请求。
4.提供健康检查端点
GET /health提供一个确定服务是否健康的端点(例如)。其他应用程序(例如负载均衡器)可以调用此端点以在服务中断时采取行动。
5. 版本化 API
确保对 API 进行版本控制并在每个请求中传递版本,这样消费者就不会受到对另一个版本的任何更改的影响。API 版本可以使用 HTTP 标头或查询/路径参数传递。即使是 API 的第一个版本(1.0)也应该明确地进行版本控制。
一些例子:
- https://api.averagecompany.com/v1/health
- https://api.averagecompany.com/health?api_version=1.0
6.接受API密钥认证
如果 API 需要由第三方调用,则允许通过 API 密钥进行身份验证是有意义的。API 密钥应使用自定义 HTTP 标头(例如Api-Key)传递。它们应该有一个到期日期,并且必须可以撤销活动密钥,以便在它们受到损害时可以使它们失效。避免将 API 密钥签入源代码控制(改用环境变量)。
7. 使用合理的 HTTP 状态码
使用传统的 HTTP 状态代码来指示请求的成功或失败。不要使用太多,并在整个 API 中为相同的结果使用相同的状态代码。一些例子:
- 200取得普遍成功
- 201为成功创作
- 400对于来自客户端的错误请求
- 401对于未经授权的请求
- 403缺少权限
- 404对于缺少的资源
- 429对于太多的请求
- 5xx对于内部错误(应不惜一切代价避免这些错误)
8.使用合理的HTTP方法
HTTP 方法有很多,但最重要的是:
- POST用于创建资源
- POST /users
- GET用于阅读资源(单个资源和集合)
- GET /users
- GET /users/{id}
- PATCH用于对资源应用部分更新
- PATCH /users/{id}
- PUT用于对资源应用完整更新(替换当前资源)
- PUT /users/{id}
- DELETE用于删除资源
- DELETE /users/{id}
9.使用不言自明、简单的名字
大多数端点都是面向资源的,应该这样命名。不要添加可以从其他地方推断出的不必要的信息。这也适用于字段名称。
好:
- GET /users=> 检索用户
- DELETE /users/{id}=> 删除用户
- POST /users/{id}/notifications=> 为特定用户创建通知
- user.first_name
- order.number
坏:
- GET /getUser
- POST /updateUser
- POST /notification/user
- order.ordernumber
- user.firstName
10. 使用标准化的错误响应
除了使用指示请求结果(成功或错误)的 HTTP 状态代码外,在返回错误时,始终使用标准化的错误响应,其中包含有关问题的更详细信息。消费者总是可以期待相同的结构并采取相应的行动。
// Request => GET /users/4TL011ax |
11. 返回创建的资源POST
POST在使用请求创建资源后返回创建的资源是个好主意。这一点很重要,因为返回的创建资源将反映底层数据源的当前状态,并将包含更新的信息(例如生成的 ID)。
// Request: POST /users |
12. PATCH好于PUT
如前所述,PATCH请求应该对资源应用部分更新,而PUT完全替换现有资源。
而围绕 PATCH 请求设计更新通常是个好主意,因为:
- 当使用PUT只更新资源的一部分字段时,仍然需要传递整个资源,这使得它更加网络密集且容易出错
- 允许任何字段不受任何限制地更新也是相当危险的
- 根据我的经验,在实践中几乎不存在任何对资源进行完整更新有意义的用例
- 想象一个order资源有一个id和一个state
- 允许消费者更新state一个order
- 状态更改更有可能由另一个端点(例如/orders/{id}/fulfill)触发
13. 尽可能具体
如上一节所述,在设计端点、命名字段以及决定接受哪些请求和响应时,尽可能具体是一个好主意。如果一个PATCH请求只接受两个字段 (name和description),则不存在错误使用它和损坏数据的危险。
14.使用分页
对所有返回资源集合并使用相同响应结构的请求进行分页。使用page_numberand page_size(或类似的)来控制要检索的块。
// Request => GET /users?page_number=1&page_size=15 |
15.允许扩展资源
允许消费者使用名为expand(或类似的)查询参数加载相关数据。这对于避免往返和一次性加载特定操作所需的所有数据特别有用。
// Request => GET /users/T9hoBuuTL4?expand=orders&expand=orders.items |