api-design-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAPI Design Patterns
API设计模式
When to Use
适用场景
Activate this skill when:
- Designing new API endpoints or modifying existing endpoint contracts
- Defining request/response schemas for a feature
- Standardizing pagination, filtering, or sorting across endpoints
- Designing a consistent error response format
- Planning API versioning or deprecation strategy
- Reviewing API contracts for consistency before implementation
- Documenting endpoint specifications for frontend/backend coordination
Input: If or exists, read for context about the feature scope and architectural decisions. Otherwise, work from the user's request directly.
plan.mdarchitecture.mdOutput: Write API design to . Tell the user: "API design written to . Run to create implementation tasks or to implement."
api-design.mdapi-design.md/task-decomposition/python-backend-expertDo NOT use this skill for:
- Writing implementation code (use )
python-backend-expert - System-level architecture decisions (use )
system-architecture - Writing tests for endpoints (use )
pytest-patterns - Frontend data fetching implementation (use )
react-frontend-expert
在以下场景激活此技能:
- 设计新API端点或修改现有端点契约
- 为功能定义请求/响应schema
- 统一各端点的分页、过滤或排序机制
- 设计一致的错误响应格式
- 规划API版本管理或弃用策略
- 实现前审查API契约的一致性
- 编写端点规范以协调前后端开发
输入: 若存在或,请读取以了解功能范围和架构决策的上下文。否则直接根据用户需求开展工作。
plan.mdarchitecture.md输出: 将API设计写入。告知用户:"API设计已写入。运行创建实现任务,或运行进行开发。"
api-design.mdapi-design.md/task-decomposition/python-backend-expert请勿在以下场景使用此技能:
- 编写实现代码(请使用)
python-backend-expert - 系统级架构决策(请使用)
system-architecture - 编写端点测试(请使用)
pytest-patterns - 前端数据获取实现(请使用)
react-frontend-expert
Instructions
操作说明
URL Naming Conventions
URL命名规范
Resource Naming Rules
资源命名规则
- Plural nouns for collections: ,
/users,/orders/products - Kebab-case for multi-word resources: ,
/order-items/user-profiles - Singular resource by ID: ,
/users/{user_id}/orders/{order_id} - Maximum 2 nesting levels: (not
/users/{user_id}/orders)/users/{user_id}/orders/{order_id}/items/{item_id} - No verbs in URLs: use HTTP methods instead (not
POST /orders)/orders/create - Query parameters for filtering, sorting, pagination:
/users?role=admin&sort=-created_at
- 集合使用复数名词:、
/users、/orders/products - 多词资源使用短横线分隔(Kebab-case):、
/order-items/user-profiles - 单个资源通过ID标识:、
/users/{user_id}/orders/{order_id} - 最多嵌套2层:(不推荐
/users/{user_id}/orders)/users/{user_id}/orders/{order_id}/items/{item_id} - URL中不使用动词:改用HTTP方法(如使用而非
POST /orders)/orders/create - 过滤、排序、分页使用查询参数:
/users?role=admin&sort=-created_at
URL Structure Template
URL结构模板
/{version}/{resource} → Collection (list, create)
/{version}/{resource}/{id} → Single resource (get, update, delete)
/{version}/{resource}/{id}/{sub-resource} → Nested collection
/{version}/{resource}/actions/{action} → Non-CRUD operations (rarely needed)/{version}/{resource} → 集合(列表、创建)
/{version}/{resource}/{id} → 单个资源(获取、更新、删除)
/{version}/{resource}/{id}/{sub-resource} → 嵌套集合
/{version}/{resource}/actions/{action} → 非CRUD操作(极少需要)Naming Examples
命名示例
| Good | Bad | Reason |
|---|---|---|
| | No verbs — HTTP method implies action |
| | POST to collection = create |
| | Kebab-case, not camelCase |
| | Max 2 nesting levels |
| | Action sub-resource for non-CRUD |
| 规范写法 | 不规范写法 | 原因 |
|---|---|---|
| | URL中不使用动词——HTTP方法已隐含操作意图 |
| | 对集合发起POST请求即表示创建资源 |
| | 使用短横线分隔(Kebab-case),而非驼峰式(camelCase) |
| | 最多嵌套2层 |
| | 非CRUD操作使用动作子资源 |
HTTP Method Semantics
HTTP方法语义
| Method | Purpose | Request Body | Success Status | Idempotent |
|---|---|---|---|---|
| Retrieve resource(s) | None | | Yes |
| Create new resource | Required | | No |
| Full replace | Required (full) | | Yes |
| Partial update | Required (partial) | | No* |
| Remove resource | None | | Yes |
*PATCH is not inherently idempotent but can be made so with proper implementation.
Response headers for creation:
- returning
POSTSHOULD include a201header with the URL of the created resourceLocation
Conditional requests:
- Support /
If-None-Matchfor caching on GET endpoints with frequently-accessed resourcesETag
| 方法 | 用途 | 请求体 | 成功状态码 | 幂等性 |
|---|---|---|---|---|
| 获取资源(单个/多个) | 无 | | 是 |
| 创建新资源 | 必填 | | 否 |
| 完全替换资源 | 必填(完整资源内容) | | 是 |
| 部分更新资源 | 必填(部分资源内容) | | 否* |
| 删除资源 | 无 | | 是 |
*PATCH本身不具备幂等性,但可通过合理设计实现幂等。
创建操作的响应头:
- 返回状态码的
201请求应包含POST响应头,指向创建后的资源URLLocation
条件请求:
- 对于频繁访问的资源,GET端点应支持/
If-None-Match以实现缓存ETag
Schema Naming Conventions (Pydantic v2)
Schema命名规范(Pydantic v2)
Follow a consistent naming pattern for all Pydantic schemas:
| Pattern | Purpose | Fields |
|---|---|---|
| POST request body | Writable fields, no id, no timestamps |
| PUT request body | All writable fields required |
| PATCH request body | All fields Optional |
| Single resource response | All fields including id, timestamps |
| Paginated list response | items + pagination metadata |
| Query parameters | Optional filter fields |
Schema design rules:
- Never expose internal fields (hashed_password, internal_notes) in Response schemas
- Always include and timestamps (
id,created_at) in Response schemasupdated_at - Use to convert ORM models to response schemas
model_validate(orm_instance) - Use for PATCH operations to distinguish "not provided" from "set to null"
model_dump(exclude_unset=True) - Reference for concrete examples
references/pydantic-schema-examples.md
为所有Pydantic schema遵循统一的命名模式:
| 命名模式 | 用途 | 字段特点 |
|---|---|---|
| POST请求体 | 仅包含可写入字段,无id和时间戳 |
| PUT请求体 | 所有可写入字段为必填 |
| PATCH请求体 | 所有字段为可选 |
| 单个资源响应 | 包含所有字段,包括id和时间戳 |
| 分页列表响应 | 包含资源列表+分页元数据 |
| 查询参数 | 可选的过滤字段 |
Schema设计规则:
- 响应Schema中绝不能暴露内部字段(如hashed_password、internal_notes)
- 响应Schema中必须包含和时间戳(
id、created_at)updated_at - 使用将ORM模型转换为响应Schema
model_validate(orm_instance) - PATCH操作使用以区分“未提供”与“设置为null”
model_dump(exclude_unset=True) - 参考获取具体示例
references/pydantic-schema-examples.md
Pagination
分页机制
Cursor-Based Pagination (Default)
基于游标分页(默认方式)
Use cursor-based pagination for all list endpoints. It is more performant than offset-based for large datasets and avoids the "shifting window" problem.
Request parameters:
GET /v1/users?cursor=eyJpZCI6MTAwfQ&limit=20| Parameter | Type | Default | Description |
|---|---|---|---|
| | | Opaque cursor from previous response |
| | | Items per page (max 100) |
Response format:
json
{
"items": [...],
"next_cursor": "eyJpZCI6MTIwfQ",
"has_more": true
}Cursor implementation:
- Encode the last item's sort key (usually ) as a base64 string
id - The cursor is opaque to the client — they must not parse or construct it
- Use — fetch one extra to determine
WHERE id > :last_id ORDER BY id ASC LIMIT :limit + 1has_more
所有列表端点默认使用基于游标分页。相较于基于偏移量的分页,它在大数据集下性能更优,且避免了“窗口偏移”问题。
请求参数:
GET /v1/users?cursor=eyJpZCI6MTAwfQ&limit=20| 参数 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| | | 来自上一次响应的不透明游标 |
| | | 每页条目数(最大100) |
响应格式:
json
{
"items": [...],
"next_cursor": "eyJpZCI6MTIwfQ",
"has_more": true
}游标实现方式:
- 将最后一个条目的排序键(通常为)编码为base64字符串
id - 游标对客户端是不透明的——客户端不得解析或自行构造游标
- 使用查询——多获取一条数据以判断
WHERE id > :last_id ORDER BY id ASC LIMIT :limit + 1的值has_more
Offset-Based Pagination (When Needed)
基于偏移量分页(按需使用)
Use offset-based only when the client needs to jump to arbitrary pages (e.g., admin tables).
json
{
"items": [...],
"total": 150,
"page": 2,
"page_size": 20,
"total_pages": 8
}仅当客户端需要跳转到任意页面时(如后台管理表格)才使用基于偏移量的分页。
json
{
"items": [...],
"total": 150,
"page": 2,
"page_size": 20,
"total_pages": 8
}Filtering and Sorting
过滤与排序
Filtering
过滤
Use query parameters with field names:
GET /v1/users?role=admin&is_active=true&created_after=2024-01-01Filtering conventions:
- Exact match:
?field=value - Range: or
?field_min=10&field_max=100?created_after=...&created_before=... - Search: (for full-text search across multiple fields)
?q=search+term - Multiple values: (OR semantics)
?status=active&status=pending
使用与字段名一致的查询参数:
GET /v1/users?role=admin&is_active=true&created_after=2024-01-01过滤规范:
- 精确匹配:
?field=value - 范围查询:或
?field_min=10&field_max=100?created_after=...&created_before=... - 全文搜索:(跨多个字段的全文搜索)
?q=search+term - 多值匹配:(OR语义)
?status=active&status=pending
Sorting
排序
Use a query parameter with field name and direction prefix:
sortGET /v1/users?sort=-created_at → descending by created_at
GET /v1/users?sort=name → ascending by name
GET /v1/users?sort=-created_at,name → multi-field sortConvention: prefix means descending, no prefix means ascending.
-使用查询参数,字段名前加方向前缀:
sortGET /v1/users?sort=-created_at → 按created_at降序排列
GET /v1/users?sort=name → 按name升序排列
GET /v1/users?sort=-created_at,name → 多字段排序规范: 前缀表示降序,无前缀表示升序。
-Error Response Format
错误响应格式
All API errors follow a consistent format:
json
{
"detail": "Human-readable error message",
"code": "MACHINE_READABLE_CODE",
"field_errors": [
{
"field": "email",
"message": "Invalid email format",
"code": "INVALID_FORMAT"
}
]
}所有API错误遵循统一格式:
json
{
"detail": "人类可读的错误信息",
"code": "机器可读的错误码",
"field_errors": [
{
"field": "email",
"message": "邮箱格式无效",
"code": "INVALID_FORMAT"
}
]
}Standard Error Codes and Status Mapping
标准错误码与状态码映射
| HTTP Status | When to Use | Example |
|---|---|---|
| Malformed request | |
| Missing or invalid authentication | |
| Authenticated but not authorized | |
| Resource not found | |
| Conflict (duplicate, version mismatch) | |
| Validation error (Pydantic) | |
| Rate limit exceeded | |
| Unexpected server error | |
Error schema (Pydantic v2):
python
class FieldError(BaseModel):
field: str
message: str
code: str
class ErrorResponse(BaseModel):
detail: str
code: str
field_errors: list[FieldError] = []| HTTP状态码 | 适用场景 | 示例 |
|---|---|---|
| 请求格式错误 | |
| 缺少或无效的身份验证 | |
| 已认证但无权限 | |
| 资源不存在 | |
| 冲突(重复、版本不匹配) | |
| 验证错误(Pydantic) | |
| 超出速率限制 | |
| 意外服务器错误 | |
错误Schema(Pydantic v2):
python
class FieldError(BaseModel):
field: str
message: str
code: str
class ErrorResponse(BaseModel):
detail: str
code: str
field_errors: list[FieldError] = []API Versioning
API版本管理
Strategy: URL Prefix Versioning
策略:URL前缀版本管理
/v1/users → Version 1
/v2/users → Version 2Versioning rules:
- Start with for all new APIs
/v1/ - Increment major version only for breaking changes
- Non-breaking changes (new optional fields, new endpoints) do NOT require a new version
- Support at most 2 active versions simultaneously
Breaking changes that require a new version:
- Removing a field from a response
- Changing a field's type
- Making an optional request field required
- Changing the URL structure for existing endpoints
- Changing error response format
Deprecation process:
- Add header to the old version:
DeprecationDeprecation: true - Add header with the retirement date:
SunsetSunset: Sat, 01 Mar 2026 00:00:00 GMT - Add header pointing to the new version:
LinkLink: </v2/users>; rel="successor-version" - Log usage of deprecated endpoints for monitoring
- Remove the old version after the sunset date
/v1/users → 版本1
/v2/users → 版本2版本管理规则:
- 所有新API以开头
/v1/ - 仅当发生破坏性变更时才升级主版本号
- 非破坏性变更(新增可选字段、新增端点)无需升级版本
- 同时最多支持2个活跃版本
需要升级版本的破坏性变更:
- 从响应中移除字段
- 改变字段类型
- 将可选请求字段设为必填
- 修改现有端点的URL结构
- 改变错误响应格式
弃用流程:
- 为旧版本添加响应头:
DeprecationDeprecation: true - 添加响应头,注明停用日期:
SunsetSunset: Sat, 01 Mar 2026 00:00:00 GMT - 添加响应头,指向新版本:
LinkLink: </v2/users>; rel="successor-version" - 记录旧版本端点的使用情况以监控
- 停用日期过后移除旧版本
OpenAPI Documentation
OpenAPI文档
FastAPI generates OpenAPI schemas automatically. Enhance them with:
python
@router.get(
"/users/{user_id}",
response_model=UserResponse,
summary="Get user by ID",
description="Retrieve a single user's details by their unique identifier.",
responses={
404: {"model": ErrorResponse, "description": "User not found"},
},
tags=["Users"],
)
async def get_user(user_id: int) -> UserResponse:
...Documentation conventions:
- Every endpoint has a (short) and optional
summary(detailed)description - Document all non-200 responses with their schema
- Group endpoints by matching the resource name
tags - Use for automatic response schema documentation
response_model
FastAPI会自动生成OpenAPI schema。可通过以下方式增强文档:
python
@router.get(
"/users/{user_id}",
response_model=UserResponse,
summary="根据ID获取用户信息",
description="通过唯一标识获取单个用户的详细信息。",
responses={
404: {"model": ErrorResponse, "description": "用户不存在"},
},
tags=["Users"],
)
async def get_user(user_id: int) -> UserResponse:
...文档规范:
- 每个端点都要有(简短描述)和可选的
summary(详细描述)description - 为所有非200响应状态码文档化对应的schema
- 按对端点分组,标签名与资源名一致
tags - 使用自动生成响应schema文档
response_model
Examples
示例
Designing a Products API Contract
设计Products API契约
Objective: Design the contract for a CRUD endpoint with search and pagination.
/v1/productsEndpoints:
| Method | Path | Description | Request | Response | Status |
|---|---|---|---|---|---|
| GET | | List products | Query: cursor, limit, q, category, sort | ProductListResponse | 200 |
| POST | | Create product | Body: ProductCreate | ProductResponse | 201 |
| GET | | Get product | — | ProductResponse | 200 |
| PATCH | | Update product | Body: ProductPatch | ProductResponse | 200 |
| DELETE | | Delete product | — | — | 204 |
Schemas:
python
class ProductCreate(BaseModel):
name: str = Field(min_length=1, max_length=200)
description: str | None = None
price_cents: int = Field(gt=0)
category: str
sku: str = Field(pattern=r"^[A-Z0-9-]+$")
class ProductPatch(BaseModel):
name: str | None = None
description: str | None = None
price_cents: int | None = Field(default=None, gt=0)
category: str | None = None
class ProductResponse(BaseModel):
id: int
name: str
description: str | None
price_cents: int
category: str
sku: str
created_at: datetime
updated_at: datetime
class ProductListResponse(BaseModel):
items: list[ProductResponse]
next_cursor: str | None
has_more: boolSearch and filtering:
GET /v1/products?q=laptop&category=electronics&sort=-price_cents&limit=20See for the full documentation template.
See for additional schema examples.
references/endpoint-catalog-template.mdreferences/pydantic-schema-examples.md目标: 设计带搜索和分页功能的 CRUD端点契约。
/v1/products端点列表:
| 方法 | 路径 | 描述 | 请求 | 响应 | 状态码 |
|---|---|---|---|---|---|
| GET | | 产品列表 | 查询参数:cursor、limit、q、category、sort | ProductListResponse | 200 |
| POST | | 创建产品 | 请求体:ProductCreate | ProductResponse | 201 |
| GET | | 获取产品详情 | — | ProductResponse | 200 |
| PATCH | | 更新产品信息 | 请求体:ProductPatch | ProductResponse | 200 |
| DELETE | | 删除产品 | — | — | 204 |
Schema定义:
python
class ProductCreate(BaseModel):
name: str = Field(min_length=1, max_length=200)
description: str | None = None
price_cents: int = Field(gt=0)
category: str
sku: str = Field(pattern=r"^[A-Z0-9-]+$")
class ProductPatch(BaseModel):
name: str | None = None
description: str | None = None
price_cents: int | None = Field(default=None, gt=0)
category: str | None = None
class ProductResponse(BaseModel):
id: int
name: str
description: str | None
price_cents: int
category: str
sku: str
created_at: datetime
updated_at: datetime
class ProductListResponse(BaseModel):
items: list[ProductResponse]
next_cursor: str | None
has_more: bool搜索与过滤示例:
GET /v1/products?q=laptop&category=electronics&sort=-price_cents&limit=20完整文档模板请参考。
更多Schema示例请参考。
references/endpoint-catalog-template.mdreferences/pydantic-schema-examples.mdEdge Cases
边缘场景
Bulk Operations
批量操作
For operations on multiple resources at once:
POST /v1/users/bulkRequest:
json
{
"items": [
{"email": "a@example.com", "name": "Alice"},
{"email": "b@example.com", "name": "Bob"}
]
}Response (partial success — status 207):
json
{
"results": [
{"index": 0, "status": "created", "data": {...}},
{"index": 1, "status": "error", "error": {"detail": "Email already exists", "code": "CONFLICT"}}
],
"succeeded": 1,
"failed": 1
}Use HTTP when individual items can succeed or fail independently.
207 Multi-Status针对多个资源的批量操作:
POST /v1/users/bulk请求体:
json
{
"items": [
{"email": "a@example.com", "name": "Alice"},
{"email": "b@example.com", "name": "Bob"}
]
}响应(部分成功——状态码207):
json
{
"results": [
{"index": 0, "status": "created", "data": {...}},
{"index": 1, "status": "error", "error": {"detail": "邮箱已存在", "code": "CONFLICT"}}
],
"succeeded": 1,
"failed": 1
}当单个条目可能独立成功或失败时,使用HTTP 状态码。
207 Multi-StatusFile Upload Endpoints
文件上传端点
File uploads use , not JSON:
multipart/form-datapython
@router.post("/v1/files", response_model=FileResponse, status_code=201)
async def upload_file(
file: UploadFile,
description: str = Form(default=""),
) -> FileResponse:
...Validate file size and MIME type before processing. Return for oversized files.
413 Payload Too Large文件上传使用,而非JSON:
multipart/form-datapython
@router.post("/v1/files", response_model=FileResponse, status_code=201)
async def upload_file(
file: UploadFile,
description: str = Form(default=""),
) -> FileResponse:
...处理前验证文件大小和MIME类型。对于过大的文件返回状态码。
413 Payload Too LargeLong-Running Operations
长时运行操作
For operations that cannot complete within a normal request timeout:
-
Returnwith a status URL:
202 Acceptedjson{"status_url": "/v1/jobs/abc123", "estimated_completion": "2024-01-15T10:30:00Z"} -
Client polls the status URL:
GET /v1/jobs/abc123 → {"status": "processing", "progress": 0.65} GET /v1/jobs/abc123 → {"status": "completed", "result_url": "/v1/reports/xyz"}
对于无法在常规请求超时内完成的操作:
-
返回状态码及状态查询URL:
202 Acceptedjson{"status_url": "/v1/jobs/abc123", "estimated_completion": "2024-01-15T10:30:00Z"} -
客户端轮询状态查询URL:
GET /v1/jobs/abc123 → {"status": "processing", "progress": 0.65} GET /v1/jobs/abc123 → {"status": "completed", "result_url": "/v1/reports/xyz"}
Sub-Resource Design
子资源设计
When a resource logically belongs to a parent but nesting would exceed 2 levels, use a top-level resource with a filter:
undefined当资源逻辑上属于父资源但嵌套层级会超过2层时,使用顶层资源加过滤参数:
undefinedInstead of: GET /v1/users/{id}/orders/{oid}/items
不推荐:GET /v1/users/{id}/orders/{oid}/items
Use: GET /v1/order-items?order_id=123
推荐: GET /v1/order-items?order_id=123
This keeps URLs flat while maintaining the relationship through filtering.
这样既保持了URL的扁平化,又通过过滤参数维持了资源间的关联关系。Output File
输出文件
Write the API design to at the project root:
api-design.mdmarkdown
undefined将API设计写入项目根目录下的:
api-design.mdmarkdown
undefinedAPI Design: [Feature Name]
API设计:[功能名称]
Endpoints
端点列表
| Method | URL | Description | Auth |
|---|---|---|---|
| GET | /v1/users | List users | Required |
| POST | /v1/users | Create user | Required |
| 方法 | URL | 描述 | 身份验证 |
|---|---|---|---|
| GET | /v1/users | 用户列表 | 必填 |
| POST | /v1/users | 创建用户 | 必填 |
Request/Response Schemas
请求/响应Schema
UserCreate
UserCreate
| Field | Type | Required | Validation |
|---|---|---|---|
| string | Yes | Valid email | |
| name | string | Yes | 1-100 chars |
| 字段 | 类型 | 必填 | 验证规则 |
|---|---|---|---|
| string | 是 | 有效的邮箱格式 | |
| name | string | 是 | 1-100个字符 |
UserResponse
UserResponse
| Field | Type | Description |
|---|---|---|
| id | uuid | User ID |
| string | User email |
| 字段 | 类型 | 描述 |
|---|---|---|
| id | uuid | 用户ID |
| string | 用户邮箱 |
Error Codes
错误码
| Code | HTTP Status | Description |
|---|---|---|
| USER_NOT_FOUND | 404 | User does not exist |
| EMAIL_EXISTS | 409 | Email already registered |
| 错误码 | HTTP状态码 | 描述 |
|---|---|---|
| USER_NOT_FOUND | 404 | 用户不存在 |
| EMAIL_EXISTS | 409 | 邮箱已注册 |
Next Steps
下一步
- Run to create implementation tasks
/task-decomposition - Run to implement endpoints
/python-backend-expert
undefined- 运行创建实现任务
/task-decomposition - 运行开发端点
/python-backend-expert
undefined