在Web开发中,API(Application Programming Interface,应用程序编程接口)是前后端交互的桥梁,也是不同系统之间通信的纽带。一个设计良好的API,能够提高开发效率,降低维护成本,提升系统的可扩展性和可维护性。
REST(Representational State Transfer,表述性状态转移)是目前最流行的API设计风格,它基于HTTP协议,利用HTTP的方法(GET、POST、PUT、DELETE等)和状态码来表示对资源的操作。RESTful API具有简单、直观、可缓存、无状态等优点,被广泛应用于Web服务和移动应用后端。
今天就来分享RESTful API设计的最佳实践,帮助大家打造优雅、易用、可维护的接口。
REST简介
1. 什么是REST:
REST是由Roy Fielding在2000年的博士论文中提出的一种软件架构风格,它不是一个标准,而是一组设计原则和约束条件。RESTful API就是遵循REST原则设计的API。
REST的核心概念是"资源"(Resource),网络上的所有事物都可以被抽象为资源,如用户、文章、评论、订单等。每个资源都有一个唯一的URI(统一资源标识符)来标识,通过HTTP方法对资源进行操作。
REST的六个约束条件:
- 客户端-服务器分离:客户端和服务器职责分离,客户端负责用户界面,服务器负责数据存储和业务逻辑,两者可以独立演进
- 无状态:服务器不保存客户端的状态,每个请求都必须包含所有必要的信息,服务器可以独立处理每个请求
- 可缓存:响应可以被缓存,减少服务器负载,提高性能
- 统一接口:使用统一的接口设计,包括资源标识、通过表示操作资源、自描述消息、超媒体作为应用状态引擎
- 分层系统:系统可以分层,客户端无法直接感知中间层,中间层可以提供负载均衡、缓存、安全等功能
- 按需代码(可选):服务器可以向客户端传输可执行代码,如JavaScript,扩展客户端功能
2. REST的核心原则:
- 一切皆资源:网络上的所有事物都可以被抽象为资源,每个资源有唯一的URI
- 通过HTTP方法操作资源:GET获取资源,POST创建资源,PUT更新资源(全量),PATCH更新资源(部分),DELETE删除资源
- 无状态:每个请求都是独立的,服务器不保存客户端状态
- 使用HTTP状态码:使用标准的HTTP状态码表示请求结果,如200成功、404未找到、500服务器错误
- 资源表示:资源可以有多种表示形式,如JSON、XML、HTML等,通过Content-Type和Accept协商
3. RESTful API的优点:
- 简单直观:基于HTTP协议,使用标准的HTTP方法和状态码,学习成本低,易于理解
- 无状态:服务器不需要保存客户端状态,易于扩展和负载均衡
- 可缓存:GET请求可以被缓存,提高性能,减少服务器负载
- 语言无关:RESTful API基于HTTP和JSON,任何语言都可以调用,跨平台性好
- 前后端分离:RESTful API天然支持前后端分离架构,前端和后端可以独立开发和部署
- 可演进性:通过版本控制和超媒体,API可以平滑演进,不破坏已有客户端
URI设计
URI(统一资源标识符)是RESTful API的入口,好的URI设计应该直观、可读、一致、可预测。
1. 使用名词,不用动词:
URI应该使用名词来表示资源,而不是动词。HTTP方法已经表示了操作(GET获取、POST创建、PUT更新、DELETE删除),URI中不需要再包含动词。
# 好的设计(名词)
GET /users # 获取用户列表
GET /users/123 # 获取ID为123的用户
POST /users # 创建用户
PUT /users/123 # 更新ID为123的用户
DELETE /users/123 # 删除ID为123的用户
# 不好的设计(动词)
GET /getUsers # 获取用户列表
GET /getUser/123 # 获取ID为123的用户
POST /createUser # 创建用户
POST /updateUser/123 # 更新ID为123的用户
POST /deleteUser/123 # 删除ID为123的用户2. 使用复数名词:
资源集合通常使用复数名词,单个资源在集合后加ID。
# 好的设计(复数)
/articles # 文章集合
/articles/123 # ID为123的文章
/articles/123/comments # ID为123的文章的评论集合
# 不推荐(单数)
/article # 文章集合
/article/123 # ID为123的文章当然,也有一些特殊情况,如某些资源天然是单数(如用户的个人资料/profile),或者某些动作无法用资源表示(如登录/login、搜索/search),这些可以例外处理。
3. 层级关系:
如果资源之间有从属关系,URI应该体现这种层级关系。
/users/123/articles # ID为123的用户的所有文章
/users/123/articles/456 # ID为123的用户的ID为456的文章
/articles/456/comments # ID为456的文章的所有评论
/articles/456/comments/789 # ID为456的文章的ID为789的评论
/categories/12/articles # ID为12的分类下的所有文章但是,层级不要太深,一般不超过2-3层,否则URI会变得复杂和难读。如果层级太深,可以考虑扁平化设计,或者通过查询参数过滤。
# 层级太深(不推荐)
/users/123/articles/456/comments/789/replies/012
# 扁平化(推荐)
/comments/789/replies # 通过评论ID获取回复,评论和文章的关系在数据中体现4. 使用连字符,不用下划线:
URI中多个单词之间使用连字符(-)分隔,而不是下划线(_)。因为在某些字体中,下划线可能被链接的下划线遮挡,而连字符更清晰可读。
# 好的设计(连字符)
/user-profiles
/article-categories
/order-items
# 不好的设计(下划线)
/user_profiles
/article_categories
/order_items5. 全小写:
URI中全部使用小写字母,因为URI是大小写敏感的(除了协议和主机名),统一使用小写可以避免混淆和重复。
# 好的设计(全小写)
/users
/articles
/article-categories
# 不好的设计(大小写混合)
/Users
/Articles
/ArticleCategories6. 文件扩展名:
URI中不应该包含文件扩展名(如.json、.xml),应该通过HTTP头的Content-Type和Accept来协商表示格式。
# 好的设计(无扩展名)
GET /users/123
Accept: application/json
# 不好的设计(带扩展名)
GET /users/123.json
GET /users/123.xmlHTTP方法
RESTful API使用标准的HTTP方法来表示对资源的操作,每个方法有明确的语义和幂等性。
1. 常用HTTP方法:
| 方法 | 语义 | 幂等 | 安全 | 典型用途 |
|---|---|---|---|---|
| GET | 获取资源 | 是 | 是 | 查询资源列表或单个资源 |
| POST | 创建资源 | 否 | 否 | 创建新资源 |
| PUT | 全量更新资源 | 是 | 否 | 完整更新已有资源 |
| PATCH | 部分更新资源 | 否 | 否 | 部分更新已有资源 |
| DELETE | 删除资源 | 是 | 否 | 删除资源 |
| HEAD | 获取资源头信息 | 是 | 是 | 检查资源是否存在,获取元数据 |
| OPTIONS | 获取资源支持的方法 | 是 | 是 | 跨域预检,查询支持的操作 |
2. 幂等性和安全性:
- 安全方法:GET、HEAD、OPTIONS,不会修改服务器资源,只是获取信息
- 幂等方法:GET、PUT、DELETE、HEAD、OPTIONS,多次执行结果相同,不会产生副作用
- 非幂等方法:POST、PATCH,多次执行可能产生不同结果或副作用
理解幂等性很重要,在网络不稳定的情况下,幂等方法可以安全重试,而非幂等方法需要谨慎处理重复提交。
3. 方法使用示例:
# 文章资源的CRUD操作
GET /articles # 获取文章列表
GET /articles/123 # 获取ID为123的文章
POST /articles # 创建新文章
PUT /articles/123 # 全量更新ID为123的文章(替换整个文章)
PATCH /articles/123 # 部分更新ID为123的文章(只更新某些字段)
DELETE /articles/123 # 删除ID为123的文章
# 子资源操作
GET /articles/123/comments # 获取文章的评论列表
POST /articles/123/comments # 为文章添加评论
DELETE /articles/123/comments/456 # 删除文章的指定评论
# 特殊操作(无法用资源表示的动作)
POST /auth/login # 登录
POST /auth/logout # 登出
POST /users/123/activate # 激活用户
POST /articles/123/publish # 发布文章对于特殊操作,如果无法用标准的CRUD表示,可以在URI后加动作动词,用POST方法执行。但是要尽量少用,优先考虑将动作抽象为资源。
HTTP状态码
RESTful API应该使用标准的HTTP状态码来表示请求结果,而不是所有请求都返回200然后在响应体中用自定义错误码。
1. 常用状态码:
| 状态码 | 含义 | 典型用途 |
|---|---|---|
| 200 OK | 请求成功 | GET、PUT、PATCH成功 |
| 201 Created | 资源创建成功 | POST创建资源成功 |
| 204 No Content | 操作成功,无返回内容 | DELETE成功,PUT/PATCH无返回 |
| 301 Moved Permanently | 永久重定向 | 资源URI变更 |
| 304 Not Modified | 资源未修改 | 缓存命中,资源未变化 |
| 400 Bad Request | 请求参数错误 | 参数缺失、格式错误、验证失败 |
| 401 Unauthorized | 未认证 | 未登录或token无效 |
| 403 Forbidden | 无权限 | 已登录但无权访问该资源 |
| 404 Not Found | 资源不存在 | 请求的资源不存在 |
| 405 Method Not Allowed | 方法不允许 | 资源不支持该HTTP方法 |
| 409 Conflict | 冲突 | 资源冲突,如重复创建、版本冲突 |
| 410 Gone | 资源已删除 | 资源曾经存在但已永久删除 |
| 415 Unsupported Media Type | 不支持的媒体类型 | 请求的Content-Type不支持 |
| 422 Unprocessable Entity | 无法处理的实体 | 参数格式正确但语义错误,如验证失败 |
| 429 Too Many Requests | 请求过多 | 限流,请求频率超限 |
| 500 Internal Server Error | 服务器内部错误 | 服务器异常,如代码bug、数据库错误 |
| 502 Bad Gateway | 网关错误 | 反向代理或网关错误 |
| 503 Service Unavailable | 服务不可用 | 服务器过载或维护中 |
| 504 Gateway Timeout | 网关超时 | 网关或代理超时 |
2. 状态码使用原则:
- 精确使用状态码:根据实际情况选择最合适的状态码,不要所有错误都返回400或500
- 不要用200表示错误:错误应该用4xx或5xx状态码,不要返回200然后在响应体中写错误码
- 201用于创建成功:POST创建资源成功应该返回201,并在Location头中指向新资源的URI
- 204用于无返回内容:DELETE成功或PUT/PATCH不需要返回内容时用204
- 401用于未认证:用户未登录或token无效时用401
- 403用于无权限:用户已登录但无权操作时用403
- 404用于资源不存在:请求的资源不存在时用404
- 422用于验证失败:参数格式正确但内容验证失败时用422(比400更精确)
- 500用于服务器错误:服务器内部异常时用500,同时记录日志便于排查
请求和响应格式
1. 请求格式:
- 使用JSON作为请求体格式(Content-Type: application/json)
- POST和PUT请求的请求体应该包含资源的字段
- PATCH请求可以只包含需要更新的字段
- 查询参数用于过滤、排序、分页、搜索等
# 创建用户的请求
POST /users
Content-Type: application/json
{
"name": "张三",
"email": "zhangsan@example.com",
"password": "secret123",
"age": 25
}
# 更新用户的请求(全量)
PUT /users/123
Content-Type: application/json
{
"name": "张三",
"email": "zhangsan@example.com",
"age": 26
}
# 更新用户的请求(部分)
PATCH /users/123
Content-Type: application/json
{
"age": 26
}
# 查询参数示例
GET /articles?category=tech&tag=php&page=2&per_page=20&sort=created_at&order=desc2. 响应格式:
- 使用JSON作为响应体格式(Content-Type: application/json)
- 响应应该包含资源的完整表示,包括ID、创建时间、更新时间等元数据
- 时间使用ISO 8601格式(如2016-05-18T20:00:00+08:00)
- 列表响应应该包含数据数组和分页信息
# 单个资源响应
GET /users/123
200 OK
Content-Type: application/json
{
"id": 123,
"name": "张三",
"email": "zhangsan@example.com",
"age": 26,
"created_at": "2016-01-01T10:00:00+08:00",
"updated_at": "2016-05-18T20:00:00+08:00"
}
# 资源列表响应
GET /articles?page=2&per_page=20
200 OK
Content-Type: application/json
{
"data": [
{
"id": 1,
"title": "文章标题",
"content": "文章内容...",
"author": {
"id": 123,
"name": "张三"
},
"created_at": "2016-05-18T20:00:00+08:00"
}
// ... 更多文章
],
"meta": {
"current_page": 2,
"per_page": 20,
"total": 156,
"total_pages": 8,
"from": 21,
"to": 40
},
"links": {
"first": "/articles?page=1&per_page=20",
"last": "/articles?page=8&per_page=20",
"prev": "/articles?page=1&per_page=20",
"next": "/articles?page=3&per_page=20"
}
}3. 错误响应格式:
错误响应应该包含错误码、错误消息、错误详情等信息,方便客户端排查问题。
# 错误响应
422 Unprocessable Entity
Content-Type: application/json
{
"error": {
"code": "VALIDATION_ERROR",
"message": "请求参数验证失败",
"details": [
{
"field": "email",
"message": "邮箱格式不正确"
},
{
"field": "password",
"message": "密码长度不能少于6位"
}
]
}
}
# 简单错误响应
404 Not Found
Content-Type: application/json
{
"error": {
"code": "NOT_FOUND",
"message": "请求的资源不存在"
}
}分页、过滤、排序、搜索
1. 分页:
列表接口应该支持分页,避免一次返回过多数据。常用的分页方式有两种:页码分页和游标分页。
# 页码分页
GET /articles?page=1&per_page=20
# 响应中包含分页信息
{
"data": [...],
"meta": {
"current_page": 1,
"per_page": 20,
"total": 156,
"total_pages": 8
}
}
# 游标分页(适合大数据量,性能更好)
GET /articles?limit=20&cursor=eyJpZCI6MTIzfQ==
# 响应中包含下一页游标
{
"data": [...],
"meta": {
"next_cursor": "eyJpZCI6NDU2fQ==",
"has_more": true
}
}2. 过滤:
通过查询参数对资源进行过滤,如按分类、标签、状态、时间范围等过滤。
# 按分类过滤
GET /articles?category=tech
# 按标签过滤
GET /articles?tag=php
# 按状态过滤
GET /articles?status=published
# 按作者过滤
GET /articles?author_id=123
# 按时间范围过滤
GET /articles?created_from=2016-01-01&created_to=2016-05-31
# 多条件组合
GET /articles?category=tech&status=published&author_id=1233. 排序:
通过查询参数指定排序字段和排序方向。
# 按创建时间倒序(最新的在前)
GET /articles?sort=created_at&order=desc
# 按浏览量倒序(最热的在前)
GET /articles?sort=views&order=desc
# 按标题正序
GET /articles?sort=title&order=asc
# 多字段排序
GET /articles?sort=is_top,created_at&order=desc,desc4. 搜索:
全文搜索通常使用q参数。
# 搜索关键词
GET /articles?q=PHP
# 搜索+过滤+分页+排序组合
GET /articles?q=PHP&category=tech&status=published&page=1&per_page=20&sort=created_at&order=desc版本控制
API会不断演进,需要版本控制来保证旧客户端的兼容性。
1. 版本控制方式:
- URI路径版本:在URI中包含版本号,如/v1/users、/v2/users,简单直观,最常用
- 查询参数版本:在查询参数中指定版本,如/users?version=1,不太推荐
- 请求头版本:在Accept头中指定版本,如Accept: application/vnd.myapp.v1+json,比较RESTful但不够直观
- 主机名版本:使用不同的子域名,如v1.api.example.com/users,适合大公司
2. 推荐方式:
推荐使用URI路径版本,简单直观,易于理解和调试。
# URI路径版本
GET /v1/users
GET /v2/users
# 同时维护多个版本
/v1/articles # 旧版本,保持兼容
/v2/articles # 新版本,新功能3. 版本演进原则:
- 向后兼容的修改不需要升版本:如添加新字段、添加新接口、添加可选参数
- 破坏性修改需要升版本:如删除字段、修改字段类型、修改接口语义、删除接口
- 旧版本要有弃用计划:提前通知用户,给出迁移时间,最终下线旧版本
- 文档中明确版本差异:说明每个版本的变更,帮助用户迁移
认证和授权
1. 认证方式:
- API Key:在请求头或查询参数中携带API Key,简单但安全性较低,适合公开API
- Basic Auth:用户名密码Base64编码,简单但安全性低,不推荐生产环境使用
- Bearer Token(JWT):使用JSON Web Token,无状态,适合前后端分离和微服务
- OAuth 2.0:第三方授权框架,适合需要第三方登录和授权的场景
- Session/Cookie:传统的会话认证,有状态,适合传统Web应用
2. 推荐方式:
对于RESTful API,推荐使用Bearer Token(JWT)方式,无状态,易于扩展和负载均衡。
# 请求头中携带Token
GET /users/123
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
# Token过期返回401
401 Unauthorized
{
"error": {
"code": "TOKEN_EXPIRED",
"message": "登录已过期,请重新登录"
}
}3. 授权:
认证解决"你是谁"的问题,授权解决"你能做什么"的问题。常用的授权模型有:
- ACL(访问控制列表):每个资源有访问控制列表,简单但灵活性差
- RBAC(基于角色的访问控制):用户分配角色,角色分配权限,最常用
- ABAC(基于属性的访问控制):根据用户、资源、环境的属性动态判断权限,灵活但复杂
RESTful API中,授权失败应该返回403 Forbidden。
# 无权限访问
403 Forbidden
{
"error": {
"code": "FORBIDDEN",
"message": "您没有权限执行此操作"
}
}安全最佳实践
- 使用HTTPS:所有API都应该使用HTTPS加密传输,防止数据被窃听和篡改
- 认证和授权:对需要保护的接口进行认证和授权,防止未授权访问
- 输入验证:对所有输入参数进行验证,防止SQL注入、XSS、CSRF等攻击
- 限流:对API进行限流,防止滥用和DDoS攻击,超过限制返回429
- 敏感信息保护:不要在响应中返回密码、密钥等敏感信息,日志中也要脱敏
- CORS配置:合理配置跨域资源共享,只允许信任的域名访问
- 安全头:设置合适的安全响应头,如X-Content-Type-Options、X-Frame-Options、X-XSS-Protection等
- 版本弃用:及时弃用和下线有安全问题的旧版本API
- 错误信息:生产环境不要返回详细的错误堆栈信息,避免泄露系统内部信息
- 审计日志:对重要操作记录审计日志,便于安全审计和问题排查
文档
API文档是API的重要组成部分,好的文档能大大降低使用者的学习成本。
1. 文档内容:
- API概述:介绍API的用途、认证方式、基础URL、版本信息
- 认证说明:如何获取和使用Token,Token过期处理
- 接口列表:每个接口的URI、HTTP方法、描述、请求参数、响应格式、错误码
- 请求示例:每个接口的请求示例,包括请求头、请求体
- 响应示例:每个接口的响应示例,包括成功和错误响应
- 错误码说明:所有可能的错误码和含义
- 变更日志:API的版本变更历史
2. 文档工具:
- Swagger/OpenAPI:最流行的API文档规范,支持自动生成交互式文档
- API Blueprint:另一种API文档规范,使用Markdown格式
- RAML:RESTful API建模语言
- Postman:API测试工具,也可以生成文档
- ReadMe、GitBook:文档托管平台
推荐使用Swagger/OpenAPI规范,可以自动生成文档和客户端代码,保持文档和代码同步。
总结
RESTful API设计是一门艺术,也是一门科学。好的API设计应该遵循REST原则,使用标准的HTTP方法和状态码,设计直观一致的URI,提供清晰的请求和响应格式,支持分页、过滤、排序、搜索,做好版本控制、认证授权、安全防护和文档。
API一旦发布,就会被使用者依赖,所以设计时要慎重,尽量保持稳定和向后兼容。同时,API也需要不断演进,通过版本控制平滑升级,在创新和兼容之间找到平衡。
希望这篇文章能帮助大家设计出优雅、易用、可维护的RESTful API,让前后端协作更加顺畅,让系统更加健壮和可扩展。
记住,好的API是给人用的,不仅仅是给机器用的。设计API时,多站在使用者的角度思考,让API简单、直观、一致、可预测,这就是最好的API设计。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录