设计优秀的 REST API 不仅仅是返回 JSON。一个设计良好的 API 应该是可预测、一致、安全且易于使用的。本指南涵盖最重要的 REST API 设计最佳实践,包括真实案例、常见错误和经过实战检验的模式。
1. 使用名词作为资源 URI
REST API 建模的是资源而不是操作。URI 应该表示事物(名词),而不是操作(动词)。HTTP 方法(GET、POST、PUT、DELETE)已经描述了操作。
# Good - nouns representing resources
GET /api/v1/users # list users
GET /api/v1/users/123 # get user 123
POST /api/v1/users # create a user
PUT /api/v1/users/123 # replace user 123
PATCH /api/v1/users/123 # partially update user 123
DELETE /api/v1/users/123 # delete user 123
# Bad - verbs describing actions
GET /api/v1/getUsers
POST /api/v1/createUser
POST /api/v1/deleteUser/123
GET /api/v1/getUserById?id=123# Nested resources (one level deep)
GET /api/v1/users/123/orders # orders for user 123
GET /api/v1/users/123/orders/456 # order 456 for user 123
POST /api/v1/users/123/orders # create order for user 123
# For actions that don't map to CRUD, use sub-resources
POST /api/v1/users/123/activate # activate user (action)
POST /api/v1/orders/456/cancel # cancel order (action)
POST /api/v1/emails/789/resend # resend email2. 正确使用 HTTP 方法
每个 HTTP 方法都有特定的语义。使用正确的方法使 API 可预测,并支持缓存、幂等性和浏览器集成。
| Method | Purpose | Idempotent | Request Body |
|---|---|---|---|
GET | Read a resource | Yes | No |
POST | Create a resource | No | Yes |
PUT | Full replacement | Yes | Yes |
PATCH | Partial update | No* | Yes |
DELETE | Remove a resource | Yes | No |
3. 使用复数资源名称
始终使用复数名词作为集合名。无论访问集合还是单个资源,URI 都保持一致。
# Good - consistent plural nouns
/api/v1/users
/api/v1/users/123
/api/v1/products
/api/v1/products/456/reviews
# Bad - mixing singular and plural
/api/v1/user # singular
/api/v1/user/123
/api/v1/productList # avoid "list" suffix4. 正确使用 HTTP 状态码
状态码告诉客户端发生了什么,无需解析响应体。最重要的三个范围是 2xx(成功)、4xx(客户端错误)和 5xx(服务器错误)。
| Code | When to Use |
|---|---|
200 OK | Successful GET, PUT, PATCH, or DELETE |
201 Created | Successful POST that creates a resource |
204 No Content | Successful DELETE with no response body |
400 Bad Request | Malformed request syntax or invalid data |
401 Unauthorized | Missing or invalid authentication |
403 Forbidden | Authenticated but not authorized |
404 Not Found | Resource does not exist |
409 Conflict | Conflicting state (e.g., duplicate email) |
422 Unprocessable | Validation errors in request body |
429 Too Many Requests | Rate limit exceeded |
500 Internal Error | Unexpected server error |
5. API 版本控制
API 版本控制在你进行破坏性变更时保护现有消费者。有三种常见策略,各有取舍。
# Strategy 1: URI versioning (most common)
GET /api/v1/users
GET /api/v2/users
# Strategy 2: Header versioning
GET /api/users
Accept: application/vnd.myapi.v2+json
# Strategy 3: Query parameter versioning
GET /api/users?version=2| Strategy | Pros | Cons |
|---|---|---|
| URI path | Simple, visible, cacheable | URI pollution |
| Header | Clean URIs | Harder to test, less visible |
| Query param | Easy to add | Cache-unfriendly, easy to forget |
6. 分页、过滤和排序
任何返回集合的端点都应支持分页。否则响应会无限增长,服务器和客户端的性能都会下降。
# Offset-based pagination (simplest)
GET /api/v1/users?page=2&limit=25
GET /api/v1/users?offset=25&limit=25
# Cursor-based pagination (better for large datasets)
GET /api/v1/users?cursor=eyJpZCI6MTAwfQ&limit=25
# Response with pagination metadata
{
"data": [...],
"pagination": {
"total": 1250,
"page": 2,
"limit": 25,
"totalPages": 50,
"hasNext": true,
"hasPrev": true
}
}
# Filtering and sorting
GET /api/v1/products?category=electronics&minPrice=100&maxPrice=500
GET /api/v1/products?sort=price&order=asc
GET /api/v1/products?sort=-created_at # prefix with - for descending
GET /api/v1/users?fields=id,name,email # sparse fieldsets7. 错误响应格式
整个 API 一致的错误响应格式有助于客户端以编程方式处理错误。包含错误代码、人类可读消息和验证错误的字段级详情。
// Consistent error response format
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{
"field": "email",
"message": "Must be a valid email address",
"value": "not-an-email"
},
{
"field": "age",
"message": "Must be at least 18",
"value": 15
}
],
"requestId": "req_abc123",
"timestamp": "2026-01-15T10:30:00Z",
"docs": "https://api.example.com/docs/errors#VALIDATION_ERROR"
}
}
// Simple error (non-validation)
{
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "User with id 999 not found",
"requestId": "req_def456"
}
}8. 认证与安全
API 安全性不可妥协。以下是每个 API 从第一天起就应实施的基本安全实践。
# Bearer token authentication (JWT)
GET /api/v1/users
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
# API key authentication
GET /api/v1/users
X-API-Key: sk_live_abc123def456
# OAuth 2.0 token request
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
&scope=read:users write:users- Always use HTTPS in production
- Never put secrets in query parameters (they appear in server logs)
- Use short-lived access tokens (15-60 min) with refresh tokens
- Implement CORS properly for browser-based clients
- Validate and sanitize all input to prevent injection attacks
- Use rate limiting to prevent brute-force attacks
9. 速率限制
速率限制保护 API 免受滥用,并确保客户端之间的公平使用。始终通过标准 HTTP 头通知限制信息。
# Rate limit response headers (standard)
HTTP/1.1 200 OK
X-RateLimit-Limit: 1000 # max requests per window
X-RateLimit-Remaining: 742 # requests remaining
X-RateLimit-Reset: 1706810400 # Unix timestamp when limit resets
Retry-After: 60 # seconds until next request (on 429)
# Rate limit exceeded response
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 60
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests. Limit: 1000/hour",
"retryAfter": 60
}
}10. HATEOAS 和链接
超媒体作为应用程序状态引擎(HATEOAS)通过在响应中包含链接来增加 API 的可发现性。
// HATEOAS response example
{
"id": 123,
"name": "John Doe",
"email": "john@example.com",
"status": "active",
"_links": {
"self": { "href": "/api/v1/users/123" },
"orders": { "href": "/api/v1/users/123/orders" },
"deactivate": {
"href": "/api/v1/users/123/deactivate",
"method": "POST"
}
}
}
// Paginated collection with HATEOAS links
{
"data": [...],
"_links": {
"self": { "href": "/api/v1/users?page=2&limit=25" },
"first": { "href": "/api/v1/users?page=1&limit=25" },
"prev": { "href": "/api/v1/users?page=1&limit=25" },
"next": { "href": "/api/v1/users?page=3&limit=25" },
"last": { "href": "/api/v1/users?page=50&limit=25" }
}
}常见问题
更新应该用 PUT 还是 PATCH?
PUT 用于完整替换资源,PATCH 用于部分更新。大多数现代 API 更倾向使用 PATCH。
REST API URI 应该小写吗?
是的,URI 应该使用小写加连字符(kebab-case)。JSON 属性名使用 camelCase。
如何处理嵌套资源?
限制嵌套在一层以内。深度嵌套的资源应该提升为顶级端点并使用查询参数。
REST API 最佳认证方式是什么?
服务器间通信用 API 密钥或 OAuth 2.0。面向用户的应用使用 OAuth 2.0 和 JWT。始终使用 HTTPS。
应该用 GraphQL 替代 REST 吗?
REST 和 GraphQL 解决不同问题。REST 更简单,缓存更好。GraphQL 适合复杂数据需求。很多团队两者结合使用。
TL;DR
- Use nouns (not verbs) for resource URIs
- Use the correct HTTP method for each operation
- Always use plural resource names
- Return appropriate HTTP status codes
- Version your API from day one (URI path is simplest)
- Support pagination, filtering, and sorting for collections
- Use a consistent error response format
- Always use HTTPS and proper authentication
- Implement rate limiting with standard headers
- Consider HATEOAS for API discoverability
从一开始就遵循这些最佳实践,可以节省大量重构时间。使用我们的工具来测试和验证 API 响应。