API Design Patterns
Conventions and best practices for designing consistent, developer-friendly REST APIs.
When to Activate
- - Designing new API endpoints
- Reviewing existing API contracts
- Adding pagination, filtering, or sorting
- Implementing error handling for APIs
- Planning API versioning strategy
- Building public or partner-facing APIs
Resource Design
URL Structure
CODEBLOCK0
Naming Rules
CODEBLOCK1
HTTP Methods and Status Codes
Method Semantics
| Method | Idempotent | Safe | Use For |
|---|
| GET | Yes | Yes | Retrieve resources |
| POST |
No | No | Create resources, trigger actions |
| PUT | Yes | No | Full replacement of a resource |
| PATCH | No* | No | Partial update of a resource |
| DELETE | Yes | No | Remove a resource |
*PATCH can be made idempotent with proper implementation
Status Code Reference
CODEBLOCK2
Common Mistakes
CODEBLOCK3
Response Format
Success Response
CODEBLOCK4
Collection Response (with Pagination)
CODEBLOCK5
Error Response
CODEBLOCK6
Response Envelope Variants
CODEBLOCK7
Pagination
Offset-Based (Simple)
CODEBLOCK8
Pros: Easy to implement, supports "jump to page N"
Cons: Slow on large offsets (OFFSET 100000), inconsistent with concurrent inserts
Cursor-Based (Scalable)
CODEBLOCK9
CODEBLOCK10
Pros: Consistent performance regardless of position, stable with concurrent inserts
Cons: Cannot jump to arbitrary page, cursor is opaque
When to Use Which
| Use Case | Pagination Type |
|---|
| Admin dashboards, small datasets (<10K) | Offset |
| Infinite scroll, feeds, large datasets |
Cursor |
| Public APIs | Cursor (default) with offset (optional) |
| Search results | Offset (users expect page numbers) |
Filtering, Sorting, and Search
Filtering
CODEBLOCK11
Sorting
CODEBLOCK12
Full-Text Search
CODEBLOCK13
Sparse Fieldsets
CODEBLOCK14
Authentication and Authorization
Token-Based Auth
CODEBLOCK15
Authorization Patterns
CODEBLOCK16
Rate Limiting
Headers
CODEBLOCK17
Rate Limit Tiers
| Tier | Limit | Window | Use Case |
|---|
| Anonymous | 30/min | Per IP | Public endpoints |
| Authenticated |
100/min | Per user | Standard API access |
| Premium | 1000/min | Per API key | Paid API plans |
| Internal | 10000/min | Per service | Service-to-service |
Versioning
URL Path Versioning (Recommended)
CODEBLOCK18
Pros: Explicit, easy to route, cacheable
Cons: URL changes between versions
Header Versioning
CODEBLOCK19
Pros: Clean URLs
Cons: Harder to test, easy to forget
Versioning Strategy
CODEBLOCK20
Implementation Patterns
TypeScript (Next.js API Route)
CODEBLOCK21
Python (Django REST Framework)
CODEBLOCK22
Go (net/http)
CODEBLOCK23
API Design Checklist
Before shipping a new endpoint:
- - [ ] Resource URL follows naming conventions (plural, kebab-case, no verbs)
- [ ] Correct HTTP method used (GET for reads, POST for creates, etc.)
- [ ] Appropriate status codes returned (not 200 for everything)
- [ ] Input validated with schema (Zod, Pydantic, Bean Validation)
- [ ] Error responses follow standard format with codes and messages
- [ ] Pagination implemented for list endpoints (cursor or offset)
- [ ] Authentication required (or explicitly marked as public)
- [ ] Authorization checked (user can only access their own resources)
- [ ] Rate limiting configured
- [ ] Response does not leak internal details (stack traces, SQL errors)
- [ ] Consistent naming with existing endpoints (camelCase vs snake_case)
- [ ] Documented (OpenAPI/Swagger spec updated)
API 设计模式
设计一致、对开发者友好的REST API的约定和最佳实践。
何时激活
- - 设计新的API端点
- 审查现有的API契约
- 添加分页、过滤或排序
- 实现API的错误处理
- 规划API版本策略
- 构建面向公众或合作伙伴的API
资源设计
URL结构
资源使用名词、复数、小写、短横线命名
GET /api/v1/users
GET /api/v1/users/:id
POST /api/v1/users
PUT /api/v1/users/:id
PATCH /api/v1/users/:id
DELETE /api/v1/users/:id
关系的子资源
GET /api/v1/users/:id/orders
POST /api/v1/users/:id/orders
不映射到CRUD的操作(谨慎使用动词)
POST /api/v1/orders/:id/cancel
POST /api/v1/auth/login
POST /api/v1/auth/refresh
命名规则
好的做法
/api/v1/team-members # 多词资源使用短横线命名
/api/v1/orders?status=active # 使用查询参数进行过滤
/api/v1/users/123/orders # 嵌套资源表示所属关系
不好的做法
/api/v1/getUsers # URL中包含动词
/api/v1/user # 使用单数(应使用复数)
/api/v1/team_members # URL中使用下划线
/api/v1/users/123/getOrders # 嵌套资源中使用动词
HTTP方法和状态码
方法语义
否 | 否 | 创建资源,触发操作 |
| PUT | 是 | 否 | 完全替换资源 |
| PATCH | 否* | 否 | 部分更新资源 |
| DELETE | 是 | 否 | 删除资源 |
*通过适当实现,PATCH可以做到幂等
状态码参考
成功
200 OK — GET、PUT、PATCH(带响应体)
201 Created — POST(包含Location头部)
204 No Content — DELETE、PUT(无响应体)
客户端错误
400 Bad Request — 验证失败,JSON格式错误
401 Unauthorized — 缺少或无效的身份验证
403 Forbidden — 已认证但未授权
404 Not Found — 资源不存在
409 Conflict — 重复条目,状态冲突
422 Unprocessable Entity — 语义无效(JSON有效,数据错误)
429 Too Many Requests — 超出速率限制
服务器错误
500 Internal Server Error — 意外故障(绝不暴露详细信息)
502 Bad Gateway — 上游服务失败
503 Service Unavailable — 临时过载,包含Retry-After
常见错误
不好的做法:所有情况都返回200
{ status: 200, success: false, error: Not found }
好的做法:语义化使用HTTP状态码
HTTP/1.1 404 Not Found
{ error: { code: not_found, message: User not found } }
不好的做法:验证错误返回500
好的做法:返回400或422并附带字段级详细信息
不好的做法:创建资源返回200
好的做法:返回201并附带Location头部
HTTP/1.1 201 Created
Location: /api/v1/users/abc-123
响应格式
成功响应
json
{
data: {
id: abc-123,
email: alice@example.com,
name: Alice,
created_at: 2025-01-15T10:30:00Z
}
}
集合响应(带分页)
json
{
data: [
{ id: abc-123, name: Alice },
{ id: def-456, name: Bob }
],
meta: {
total: 142,
page: 1,
per_page: 20,
total_pages: 8
},
links: {
self: /api/v1/users?page=1&per_page=20,
next: /api/v1/users?page=2&per_page=20,
last: /api/v1/users?page=8&per_page=20
}
}
错误响应
json
{
error: {
code: validation_error,
message: Request validation failed,
details: [
{
field: email,
message: Must be a valid email address,
code: invalid_format
},
{
field: age,
message: Must be between 0 and 150,
code: outofrange
}
]
}
}
响应封装变体
typescript
// 选项A:带数据包装器的封装(推荐用于公共API)
interface ApiResponse {
data: T;
meta?: PaginationMeta;
links?: PaginationLinks;
}
interface ApiError {
error: {
code: string;
message: string;
details?: FieldError[];
};
}
// 选项B:扁平响应(更简单,常见于内部API)
// 成功:直接返回资源
// 错误:返回错误对象
// 通过HTTP状态码区分
分页
基于偏移量(简单)
GET /api/v1/users?page=2&per_page=20
实现
SELECT * FROM users
ORDER BY created_at DESC
LIMIT 20 OFFSET 20;
优点: 易于实现,支持跳转到第N页
缺点: 大偏移量时性能慢(OFFSET 100000),并发插入时不一致
基于游标(可扩展)
GET /api/v1/users?cursor=eyJpZCI6MTIzfQ&limit=20
实现
SELECT * FROM users
WHERE id > :cursor_id
ORDER BY id ASC
LIMIT 21; -- 多取一条以确定是否有下一页
json
{
data: [...],
meta: {
has_next: true,
next_cursor: eyJpZCI6MTQzfQ
}
}
优点: 无论位置如何性能一致,并发插入时稳定
缺点: 无法跳转到任意页面,游标不透明
何时使用哪种
| 使用场景 | 分页类型 |
|---|
| 管理后台、小数据集(<1万) | 偏移量 |
| 无限滚动、信息流、大数据集 |
游标 |
| 公共API | 游标(默认)配合偏移量(可选) |
| 搜索结果 | 偏移量(用户期望页码) |
过滤、排序和搜索
过滤
简单等值查询
GET /api/v1/orders?status=active&customer_id=abc-123
比较运算符(使用方括号表示法)
GET /api/v1/products?price[gte]=10&price[lte]=100
GET /api/v1/orders?created_at[after]=2025-01-01
多个值(逗号分隔)
GET /api/v1/products?category=electronics,clothing
嵌套字段(点号表示法)
GET /api/v1/orders?customer.country=US
排序
单字段(前缀-表示降序)
GET /api/v1/products?sort=-created_at
多字段(逗号分隔)
GET /api/v1/products?sort=-featured,price,-created_at
全文搜索
搜索查询参数
GET /api/v1/products?q=wireless+headphones
字段特定搜索
GET /api/v1/users?email=alice
稀疏字段集
只返回指定字段(减少负载)
GET /api/v1/users?fields=id,name,email
GET /api/v1/orders?fields=id,total,status&include=customer.name
身份验证和授权
基于令牌的身份验证
Authorization头中的Bearer令牌
GET /api/v1/users
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
API密钥(用于服务器到服务器)
GET /api/v1/data
X-API-Key: sk
liveabc123
授权模式
typescript
// 资源级别:检查所有权
app.get(/api/v1/orders/:id, async (req, res) =>