REST(Representational State Transfer)是一种软件架构风格,用于设计网络应用程序的接口。
REST API(Application Programming Interface)是基于 REST 原则构建的 Web 服务接口,它允许不同的系统通过 HTTP 协议进行通信和数据交换。
REST API 的核心特点包括:
- 无状态性(Stateless):每个请求都包含处理该请求所需的全部信息
- 资源导向(Resource-based):所有数据被视为资源,通过 URI 标识
- 统一接口(Uniform Interface):使用标准 HTTP 方法(GET、POST、PUT、DELETE 等)
- 可缓存性(Cacheable):响应可以明确标记为可缓存或不可缓存
REST API 的核心概念
1. 资源(Resource)
在 REST 中,资源是任何可以命名的信息,如用户、产品、订单等。每个资源都有一个唯一的标识符(URI)。
2. HTTP 方法
REST API 使用标准 HTTP 方法来定义对资源的操作:
HTTP 方法 | 描述 | 幂等性 | 安全性 |
---|---|---|---|
GET | 获取资源 | 是 | 是 |
POST | 创建新资源 | 否 | 否 |
PUT | 更新整个资源 | 是 | 否 |
PATCH | 部分更新资源 | 否 | 否 |
DELETE | 删除资源 | 是 | 否 |
3. 状态码
HTTP 状态码表示请求的处理结果:
状态码 | 类别 | 常见状态码 |
---|---|---|
2xx | 成功 | 200 OK, 201 Created |
3xx | 重定向 | 301 Moved Permanently |
4xx | 客户端错误 | 400 Bad Request, 404 Not Found |
5xx | 服务器错误 | 500 Internal Server Error |
4. 数据格式
REST API 常用的数据交换格式:
- JSON(JavaScript Object Notation)
- XML(eXtensible Markup Language)
- 有时也使用 YAML、CSV 等格式
REST API 设计最佳实践
1. URI 设计原则
- 使用名词而非动词表示资源
- 好:
/users
- 不好:
/getUsers
- 好:
- 使用小写字母和连字符(-)
- 避免文件扩展名
- 使用复数形式表示集合
- 分层次表示关系:
/users/{id}/orders
2. 版本控制
建议在 URI 或请求头中包含 API 版本信息:
- URI 路径:
/v1/users
- 请求头:
Accept: application/vnd.myapi.v1+json
3. 过滤、排序和分页
对于集合资源,提供查询参数:
- 过滤:
/users?role=admin
- 排序:
/users?sort=-created_at
- 分页:
/users?page=2&limit=10
4. 安全性
- 使用 HTTPS
- 实施身份验证(OAuth2、JWT)
- 限制请求频率
- 验证输入数据
REST API 示例
用户管理 API 示例
实例
GET /api/v1/users
Accept: application/json
# 创建新用户
POST /api/v1/users
Content-Type: application/json
{
"name": "张三",
"email": "zhangsan@example.com"
}
# 获取特定用户
GET /api/v1/users/123
Accept: application/json
# 更新用户信息
PUT /api/v1/users/123
Content-Type: application/json
{
"name": "张三(更新)",
"email": "new-email@example.com"
}
# 删除用户
DELETE /api/v1/users/123
响应示例
实例
{
"status": "success",
"data": {
"id": 123,
"name": "张三",
"email": "zhangsan@example.com",
"created_at": "2023-01-01T00:00:00Z"
}
}
// 错误响应
{
"status": "error",
"message": "User not found",
"code": 404
}
测试 REST API 的工具
- Postman:功能强大的 API 测试工具
- cURL:命令行工具
- Insomnia:轻量级 API 测试客户端
- Swagger/OpenAPI:API 文档和测试工具
cURL 示例
实例
curl -X GET https://api.example.com/users/123 \
-H "Accept: application/json"
# POST 请求
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-d '{"name":"李四","email":"lisi@example.com"}'
REST API 开发框架
根据编程语言不同,有多种框架可用于开发 REST API:
语言 | 流行框架 |
---|---|
JavaScript | Express.js, NestJS |
Python | Django REST Framework, Flask |
Java | Spring Boot |
PHP | Laravel, Symfony |
Ruby | Ruby on Rails |
Go | Gin, Echo |
一、基础设计原则
1. 采用明确的命名约定
基本原则:
- 使用名词(而非动词)表示资源
- 使用复数形式表示集合
- 保持命名的一致性和直观性
示例:
✅ 好的设计: /users, /products, /orders
-
❌ 不好的设计: /getUsers, /addProduct, /all-orders
扩展建议:
- 对资源采用层次化命名,反映资源间的关系:
/companies/{companyId}/departments/{departmentId}/employees
- 为了提高可读性,在多个单词的资源名称中使用连字符(kebab-case):
/shipping-addresses
而非/shippingaddresses
- 在API版本迭代时保持命名一致性,避免不必要的变更造成客户端混淆
2. 正确使用HTTP方法
基本用法:
- GET:读取资源(幂等)
- POST:创建新资源
- PUT:完全更新资源(幂等)
- PATCH:部分更新资源
- DELETE:删除资源(幂等)
示例:
GET /users # 获取用户列表 GET /users/123 # 获取特定用户 POST /users # 创建新用户 PUT /users/123 # 完全更新用户 PATCH /users/123 # 部分更新用户 DELETE /users/123 # 删除用户
扩展建议:
- 理解幂等性的重要性:GET、PUT、DELETE是幂等操作,多次调用产生相同结果
- 对于批量操作,考虑使用POST而非PUT,因为PUT通常期望客户端明确资源标识符
- 在设计PATCH操作时,考虑使用JSON Patch (RFC 6902) 或 JSON Merge Patch (RFC 7386) 标准格式
- 对于复杂操作,可以使用资源扩展:
POST /users/123/activate
比POST /activateUser/123
更符合REST原则
3. 使用合适的HTTP状态码
常用状态码:
- 200 OK:请求成功
- 201 Created:资源创建成功
- 204 No Content:成功但无返回内容(如DELETE操作)
- 400 Bad Request:客户端错误
- 401 Unauthorized:未认证
- 403 Forbidden:无权限
- 404 Not Found:资源不存在
- 409 Conflict:资源冲突
- 500 Internal Server Error:服务器错误
扩展建议:
- 使用更详细的状态码提高API的表达能力:
- 429 Too Many Requests:请求频率超限
- 405 Method Not Allowed:不支持的HTTP方法
- 415 Unsupported Media Type:不支持的内容类型
- 422 Unprocessable Entity:语义错误
- 为了简化客户端处理,可以在主要错误类别内保持一致:客户端错误(4xx)和服务器错误(5xx)
- 始终与状态码一起提供有意义的错误消息和错误代码
二、查询和过滤设计
4. 实现有效的分页
基本实现:
- 使用
limit
和offset
或page
和size
参数 - 在响应中包含分页元数据
示例:
GET /products?limit=20&offset=40 GET /products?page=3&size=20
响应示例:
{ "data": [...], "pagination": { "total": 523, "pages": 27, "current_page": 3, "per_page": 20, "next": "/products?page=4&size=20", "prev": "/products?page=2&size=20" } }
扩展建议:
- 考虑使用基于游标的分页,特别是处理大型数据集或频繁更新的数据时
- 设置合理的默认分页值和最大限制,防止过大的请求影响性能
- 在分页响应中提供HATEOAS链接,便于客户端导航(如上例中的next/prev链接)
- 对时间序列数据,可以使用基于时间的分页:
GET /events?since=2023-01-01T00:00:00Z&until=2023-01-31T23:59:59Z
5. 提供灵活的过滤、排序和搜索
基本实现:
- 使用查询参数进行过滤:
?status=active
- 使用
sort
参数进行排序:?sort=created_at
- 支持多字段排序和升/降序:
?sort=price:asc,rating:desc
示例:
GET /products?category=electronics&price_min=100&price_max=500&sort=price:asc GET /users?role=admin&search=john
扩展建议:
- 为复杂查询提供表达式语法:
?price=gt:100,lt:500
- 实现部分匹配和模糊搜索选项:
?name=like:john
- 支持字段选择,允许客户端指定需要的字段:
?fields=id,name,email
- 考虑为特别复杂的查询实现GraphQL端点作为补充
- 对于常用的过滤组合,提供预定义的过滤器:
?filter=recent
可能等同于?created_after=30days&sort=created_at:desc
6. 实现有效的API版本控制
主要方法:
- URL路径版本:
/api/v1/users
- 查询参数版本:
/api/users?version=1
- 请求头版本:
Accept: application/vnd.company.v1+json
扩展建议:
- URL路径版本最直观,但会导致URI不稳定
- 请求头版本保持URI稳定,但对客户端来说不够直观
- 在版本迭代中遵循语义化版本控制原则:
- 向后兼容的变更使用次要版本号(v1.1)
- 不兼容的变更使用主要版本号(v2)
- 在新旧版本之间提供迁移期,允许客户端平滑过渡
- 在文档中明确标注每个API版本的生命周期状态:开发中、稳定、弃用、停用
三、响应设计
7. 设计一致的响应结构
基本结构:
- 使用包装对象,区分数据和元数据
- 保持错误响应格式一致
成功响应示例:
{ "status": "success", "data": { "id": 123, "name": "Example Product", "price": 99.99 }, "meta": { "timestamp": "2023-06-15T08:30:00Z" } }
错误响应示例:
{ "status": "error", "error": { "code": "VALIDATION_ERROR", "message": "Invalid input data", "details": [ {"field": "email", "message": "Must be a valid email address"} ] }, "meta": { "timestamp": "2023-06-15T08:30:00Z", "request_id": "req-123456" } }
扩展建议:
- 在错误响应中包含唯一的错误代码,便于故障排除和文档参考
- 对于复杂错误,提供结构化的错误详情,特别是表单验证错误
- 包含请求标识符(request_id),便于日志跟踪和客户支持
- 考虑国际化支持,提供错误消息的多语言版本或错误代码映射
- 避免在错误消息中泄露敏感信息或实现细节
8. 实现HATEOAS原则
基本概念:
- HATEOAS(Hypermedia as the Engine of Application State)
- 在响应中包含相关资源链接,使API具有自描述性
示例:
{ "data": { "id": 123, "name": "John Doe" }, "links": { "self": "/users/123", "orders": "/users/123/orders", "update": {"href": "/users/123", "method": "PUT"}, "delete": {"href": "/users/123", "method": "DELETE"} } }
扩展建议:
- 使用标准化的链接关系名称(如HAL或JSON:API规范中定义的)
- 包含链接的上下文信息,如HTTP方法和所需的媒体类型
- 根据用户权限动态生成链接,只显示当前用户可用的操作
- 考虑使用JSON Schema提供输入数据格式的自描述
9. 选择适当的序列化格式
常用格式:
- JSON:最常用,轻量级且易于解析
- XML:更严格但更冗长
- MessagePack:二进制格式,适合性能敏感场景
扩展建议:
- 使用内容协商(Content Negotiation)支持多种格式:客户端通过
Accept
头指定期望的格式 - 对于JSON,遵循一致的命名约定(camelCase或snake_case)
- 考虑特殊场景需求:
- CSV格式适合导出大量数据
- Protocol Buffers或gRPC适合高性能微服务间通信
- JSON-LD适合需要语义数据的场景
- 为不同格式提供一致的数据模型和字段名称
四、安全与性能
10. 实施有效的身份验证和授权
常用方法:
- API密钥:适用于服务间通信
- OAuth 2.0:适用于第三方授权
- JWT(JSON Web Tokens):适用于无状态认证
扩展建议:
- 为不同的API使用场景选择合适的授权流程:
- 用户到服务器:授权码流程
- 服务器到服务器:客户端凭证流程
- 实施细粒度的权限控制,遵循最小权限原则
- 实现API密钥轮换和撤销机制
- 使用标准的OAuth 2.0范围(scopes)定义权限
- 考虑实现基于属性的访问控制(ABAC)用于复杂授权场景
11. 实施速率限制和节流
基本实现:
- 使用请求速率限制保护API
- 在响应头中提供限制信息
响应头示例:
X-RateLimit-Limit: 100 X-RateLimit-Remaining: 95 X-RateLimit-Reset: 1623760800
扩展建议:
- 实现多层级限制:
- 按IP地址限制(防止匿名滥用)
- 按用户/API密钥限制(公平使用)
- 按资源/端点限制(保护敏感操作)
- 提供高级计划或按需扩展选项
- 使用令牌桶或漏桶算法处理突发流量
- 实现自适应节流,根据系统负载动态调整限制
- 为关键客户端提供优先级通道或保留容量
12. 适当使用缓存
基本实现:
- 使用ETags和If-None-Match头
- 设置适当的Cache-Control指令
示例:
Cache-Control: max-age=3600, must-revalidate ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
扩展建议:
- 根据资源类型设置不同的缓存策略:
- 静态内容:较长的max-age
- 个人资料:较短的max-age或private指令
- 频繁变化的数据:使用ETag而非max-age
- 实现条件请求(If-Modified-Since, If-None-Match)减少带宽使用
- 考虑在API网关或CDN层面实现缓存
- 提供缓存失效机制,特别是对于突然需要更新的资源
- 使用缓存标签(Cache Tags)进行细粒度缓存管理
13. 支持内容压缩
基本实现:
- 支持gzip和Brotli压缩
- 使用Accept-Encoding和Content-Encoding头
扩展建议:
- 对大型响应自动应用压缩,但避免对小型响应(<1KB)压缩
- 为不同的内容类型优化压缩级别
- 在性能敏感场景下,考虑预压缩常用响应
- 监控压缩比和CPU使用情况,找到最佳平衡点
- 考虑在代理/网关层处理压缩,减轻应用服务器负担
五、文档与可维护性
14. 提供全面的API文档
基本实现:
- 使用OpenAPI/Swagger规范
- 包含示例请求和响应
扩展建议:
- 采用"文档即代码"方法,将API规范与代码一起版本控制
- 提供互动式API浏览器,允许开发者直接测试API
- 包含常见用例和集成场景的教程
- 为每个端点提供示例代码片段(多种编程语言)
- 实现API变更日志,清晰标注废弃和新增功能
- 考虑创建开发者社区或论坛,促进知识共享
15. 监控和日志记录
基本实现:
- 记录请求/响应时间
- 跟踪错误率和使用模式
扩展建议:
- 实现分布式跟踪,使用W3C Trace Context或类似标准
- 设置多维度监控指标:
- 端点性能(P95/P99延迟)
- 错误率和类型分布
- 客户端使用模式
- 资源消耗(CPU、内存、带宽)
- 实现智能告警,检测异常模式而非简单阈值
- 提供开发者控制台,允许API消费者查看自己的使用统计
- 使用结构化日志格式(如JSON),便于日志分析和搜索
16. 提供有用的错误调试信息
基本实现:
- 提供明确的错误消息
- 包含唯一的错误代码
扩展建议:
- 根据环境调整错误详细程度(生产环境中避免泄露敏感信息)
- 实现错误中心,将错误代码映射到详细的故障排除指南
- 提供上下文敏感的帮助链接
- 为常见错误提供自动化修复建议
- 在开发环境中提供更详细的堆栈跟踪和上下文
- 对关键错误实现自动报告机制
六、高级设计考虑
17. 批量处理和异步操作
批量处理:
- 支持批量创建、更新和删除操作
- 提供部分成功处理选项
批量操作示例:
POST /users/batch
{ "operations": [ {"method": "POST", "path": "/users", "body": {"name": "User 1"}}, {"method": "PUT", "path": "/users/123", "body": {"name": "Updated User"}} ] }
异步操作:
- 对于长时间运行的任务,返回202 Accepted
- 提供任务状态端点
异步流程示例:
POST /reports/generate Response: 202 Accepted Location: /tasks/abc-123 GET /tasks/abc-123 Response: {"status": "processing", "progress": 45, "eta": "30s"} GET /tasks/abc-123 Response: {"status": "completed", "result": "/reports/xyz-789"}
扩展建议:
- 实现基于webhook的异步通知,在任务完成时回调客户端
- 对批量操作提供原子性选项(全部成功或全部失败)
- 支持批量操作中的依赖关系(一个操作依赖另一个操作的结果)
- 提供任务优先级机制和取消能力
- 实现任务重试策略和故障处理机制
18. 考虑API设计的演化
基本原则:
- 使用新增而非修改
- 避免删除,而是废弃后再删除
- 维护向后兼容性
扩展建议:
- 制定明确的API生命周期政策:
- 预览/Alpha/Beta版本的稳定性预期
- 废弃周期(通常至少6-12个月)
- 长期支持(LTS)版本的维护期
- 使用特性标志(Feature Flags)逐步推出新功能
- 实现API使用分析,了解哪些端点和功能已不再使用
- 提供迁移工具和示例,帮助客户端过渡到新版本
- 考虑设计时的扩展点,如自定义字段或元数据支持
七、行业特定优化与新趋势
移动应用API优化
- 实现GraphQL端点,允许移动客户端精确请求所需数据
- 支持部分响应,减少带宽使用:
?fields=id,name,thumbnail
- 提供批量预加载API,减少网络往返
- 考虑响应式设计,根据设备能力和网络条件调整响应大小
- 实现增量同步机制,只传输变更数据
物联网(IoT)API考虑
- 支持轻量级协议(如MQTT或CoAP)
- 实现设备状态模型和双向通信
- 优化带宽使用,使用二进制格式和压缩
- 设计离线操作和冲突解决策略
- 提供设备管理和固件更新API
API优先开发方法
- 采用设计优先(Design-First)而非代码优先(Code-First)方法
- 使用API模型驱动开发流程(例如从OpenAPI规范生成代码)
- 实施API设计评审流程,确保一致性和质量
- 建立API风格指南和最佳实践文档
- 使用契约测试确保实现符合规范
八、总结
设计良好的REST API需要仔细平衡多种因素,包括可用性、性能、安全性和可维护性。通过遵循这些最佳实践,开发团队可以创建既符合REST原则又满足现代应用需求的API。关键是保持一致性、直观性,并始终从API消费者的角度思考。随着API经济的不断发展,优质的API设计将成为组织成功的关键因素。
九、延伸阅读
- RESTful Web APIs (Leonard Richardson, Mike Amundsen)
- API设计模式 (JJ Geewax)
- Web API设计:构建现代应用的最佳实践 (Arnaud Lauret)
- REST API安全指南 (OWASP)
- Richardson成熟度模型:了解REST API的演进层次
点我分享笔记