关于 api-design-patterns
api-design-patterns 是用于实现前设计和审查 API 契约的 skill,涵盖端点命名、架构、分页、错误、版本管理和文档,并将设计写入 api-design.md。
主要功能
- 01规定 URL 与 HTTP 方法规范
- 02统一请求和响应架构
- 03涵盖分页、筛选与排序
- 04定义错误格式和版本管理
- 05指导 OpenAPI 文档编写
适用场景
- 01设计新的 API 契约
- 02审查端点一致性
- 03规划分页与筛选
- 04定义 API 错误与版本策略
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ hieutrtr/ ai1-skills --skill api-design-patterns安装后在 Claude Code 中输入/api-design-patterns确认已加载。
附带文件references/endpoint-catalog-template.mdreferences/pydantic-schema-examples.md
详细介绍
API 设计模式
适用场景
在以下情况下启用此技能:
- 设计新的 API 端点或修改现有端点契约
- 为某项功能定义请求/响应架构
- 统一各端点的分页、筛选或排序方式
- 设计一致的错误响应格式
- 规划 API 版本管理或弃用策略
- 在实施前检查 API 契约的一致性
- 编写端点规范文档,以协调前端和后端工作
输入: 如果存在 plan.md 或 architecture.md,请阅读以了解功能范围和架构决策。否则,直接根据用户请求开展工作。
输出: 将 API 设计写入 api-design.md。告知用户:“API 设计已写入 api-design.md。运行 /task-decomposition 创建实施任务,或运行 /python-backend-expert 进行实现。”
请勿将此技能用于:
- 编写实现代码(使用
python-backend-expert) - 制定系统级架构决策(使用
system-architecture) - 为端点编写测试(使用
pytest-patterns) - 实现前端数据获取(使用
react-frontend-expert)
说明
URL 命名约定
资源命名规则
- 集合使用复数名词:
/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 结构模板
/{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)
命名示例
| 良好示例 | 不良示例 | 原因 |
|---|---|---|
GET /v1/users | GET /v1/getUsers | 不使用动词——HTTP 方法已表明操作 |
POST /v1/users | POST /v1/user/create | 对集合执行 POST = 创建 |
GET /v1/order-items | GET /v1/orderItems | 使用 kebab-case,而不是 camelCase |
GET /v1/users/{id}/orders | GET /v1/users/{id}/orders/{oid}/items | 最多嵌套 2 层 |
POST /v1/orders/{id}/actions/cancel | POST /v1/cancelOrder/{id} | 非 CRUD 操作使用动作子资源 |
HTTP 方法语义
| 方法 | 用途 | 请求正文 | 成功状态 | 幂等 |
|---|---|---|---|---|
GET | 获取资源 | 无 | 200 OK | 是 |
POST | 创建新资源 | 必需 | 201 Created | 否 |
PUT | 完整替换 | 必需(完整内容) | 200 OK | 是 |
PATCH | 部分更新 | 必需(部分内容) | 200 OK | 否* |
DELETE | 删除资源 | 无 | 204 No Content | 是 |
*PATCH 本身并非天然幂等,但可以通过适当实现使其幂等。
创建操作的响应头:
- 返回
201的POST应包含Location响应头,其中提供所创建资源的 URL
条件请求:
- 对频繁访问资源的 GET 端点,支持使用
If-None-Match/ETag进行缓存
Schema 命名约定(Pydantic v2)
所有 Pydantic schema 均遵循一致的命名模式:
| 模式 | 用途 | 字段 |
|---|---|---|
{Resource}Create | POST 请求体 | 可写字段,不包含 id 和时间戳 |
{Resource}Update | PUT 请求体 | 所有可写字段均为必填 |
{Resource}Patch | PATCH 请求体 | 所有字段均为 Optional |
{Resource}Response | 单个资源响应 | 所有字段,包括 id 和时间戳 |
{Resource}ListResponse | 分页列表响应 | items + 分页元数据 |
{Resource}Filter | 查询参数 | 可选筛选字段 |
Schema 设计规则:
- Response schema 中不得暴露内部字段(hashed_password、internal_notes)
- Response schema 中始终包含
id和时间戳(created_at、updated_at) - 使用
model_validate(orm_instance)将 ORM 模型转换为响应 schema - PATCH 操作使用
model_dump(exclude_unset=True),以区分“未提供”和“设为 null” - 具体示例请参阅
references/pydantic-schema-examples.md
分页
基于游标的分页(默认)
所有列表端点均使用基于游标的分页。对于大型数据集,这种方式的性能优于基于偏移量的分页,也能避免“窗口移动”问题。
请求参数:
GET /v1/users?cursor=eyJpZCI6MTAwfQ&limit=20
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
cursor | str | None | None | 来自先前响应的不透明游标 |
limit | int | 20 | 每页项目数(最多 100) |
响应格式:
{
"items": [...],
"next_cursor": "eyJpZCI6MTIwfQ",
"has_more": true
}
游标实现:
- 将最后一项的排序键(通常为
id)编码为 base64 字符串 - 游标对客户端是不透明的——客户端不得解析或构造游标
- 使用
WHERE id > :last_id ORDER BY id ASC LIMIT :limit + 1——多获取一项,以确定has_more
基于偏移量的分页(按需使用)
仅当客户端需要跳转到任意页面时(例如,管理后台表格)才使用基于偏移量的分页。
{
"items": [...],
"total": 150,
"page": 2,
"page_size": 20,
"total_pages": 8
}
筛选与排序
筛选
使用字段名作为查询参数:
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(用于跨多个字段进行全文搜索) - 多个值:
?status=active&status=pending(OR 语义)
排序
使用 sort 查询参数,并指定字段名和方向前缀:
GET /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 sort
**约定:**前缀 - 表示降序;无前缀表示升序。
错误响应格式
所有 API 错误均遵循一致的格式:
{
"detail": "Human-readable error message",
"code": "MACHINE_READABLE_CODE",
"field_errors": [
{
"field": "email",
"message": "Invalid email format",
"code": "INVALID_FORMAT"
}
]
}
标准错误代码与状态映射
| HTTP 状态 | 使用场景 | 示例 code |
|---|---|---|
400 | 请求格式错误 | BAD_REQUEST |
401 | 缺少身份验证信息或身份验证无效 | UNAUTHORIZED |
403 | 已通过身份验证,但未获授权 | FORBIDDEN |
404 | 未找到资源 | NOT_FOUND |
409 | 冲突(重复、版本不匹配) | CONFLICT |
422 | 验证错误(Pydantic) | VALIDATION_ERROR |
429 | 超出速率限制 | RATE_LIMITED |
500 | 意外的服务端错误 | INTERNAL_ERROR |
错误 schema(Pydantic v2):
class FieldError(BaseModel):
field: str
message: str
code: str
class ErrorResponse(BaseModel):
detail: str
code: str
field_errors: list[FieldError] = []
API 版本控制
策略:URL 前缀版本控制
/v1/users → Version 1
/v2/users → Version 2
版本控制规则:
- 所有新 API 都从
/v1/开始 - 仅在有破坏性变更时递增主版本号
- 非破坏性变更(新增可选字段、新增端点)不需要新版本
- 同时最多支持 2 个活跃版本
需要新版本的破坏性变更:
- 从响应中移除字段
- 更改字段类型
- 将可选请求字段设为必填
- 更改现有端点的 URL 结构
- 更改错误响应格式
弃用流程:
- 为旧版本添加
Deprecation标头:Deprecation: true - 添加带有退役日期的
Sunset标头:Sunset: Sat, 01 Mar 2026 00:00:00 GMT - 添加指向新版本的
Link标头:Link: </v2/users>; rel="successor-version" - 记录已弃用端点的使用情况以进行监控
- 在日落日期之后移除旧版本
OpenAPI 文档
FastAPI 会自动生成 OpenAPI 架构。通过以下方式完善文档:
@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:
...
文档约定:
- 每个端点都有一个
summary(简短)和可选的description(详细) - 使用对应的架构记录所有非 200 响应
- 按与资源名称匹配的
tags对端点分组 - 使用
response_model自动记录响应架构
示例
设计 Products API 契约
目标: 为带搜索和分页功能的 /v1/products CRUD 端点设计契约。
端点:
| 方法 | 路径 | 描述 | 请求 | 响应 | 状态 |
|---|---|---|---|---|---|
| GET | /v1/products | 列出产品 | 查询:cursor、limit、q、category、sort | ProductListResponse | 200 |
| POST | /v1/products | 创建产品 | 请求体:ProductCreate | ProductResponse | 201 |
| GET | /v1/products/{id} | 获取产品 | — | ProductResponse | 200 |
| PATCH | /v1/products/{id} | 更新产品 | 请求体:ProductPatch | ProductResponse | 200 |
| DELETE | /v1/products/{id} | 删除产品 | — | — | 204 |
架构:
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
完整的文档模板请参见 references/endpoint-catalog-template.md。
更多架构示例请参见 references/pydantic-schema-examples.md。
边缘情况
批量操作
对于一次处理多个资源的操作:
POST /v1/users/bulk
请求:
{
"items": [
{"email": "[email protected]", "name": "Alice"},
{"email": "[email protected]", "name": "Bob"}
]
}
响应(部分成功 — 状态码 207):
{
"results": [
{"index": 0, "status": "created", "data": {...}},
{"index": 1, "status": "error", "error": {"detail": "Email already exists", "code": "CONFLICT"}}
],
"succeeded": 1,
"failed": 1
}
当各个项目可以独立成功或失败时,使用 HTTP 207 Multi-Status。
文件上传端点
文件上传使用 multipart/form-data,而非 JSON:
@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 Large。
长时间运行的操作
对于无法在正常请求超时时间内完成的操作:
-
返回带有状态 URL 的
202 Accepted:{"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"}
子资源设计
当资源在逻辑上属于父资源,但嵌套层级会超过 2 层时,使用带筛选条件的顶层资源:
# Instead of: GET /v1/users/{id}/orders/{oid}/items
# Use: GET /v1/order-items?order_id=123
这样既能保持 URL 简洁,又能通过筛选条件维护关联关系。
输出文件
将 API 设计写入项目根目录下的 api-design.md:
# API Design: [Feature Name]
## Endpoints
| Method | URL | Description | Auth |
|--------|-----|-------------|------|
| GET | /v1/users | List users | Required |
| POST | /v1/users | Create user | Required |
## Request/Response Schemas
### UserCreate
| Field | Type | Required | Validation |
|-------|------|----------|------------|
| email | string | Yes | Valid email |
| name | string | Yes | 1-100 chars |
### UserResponse
| Field | Type | Description |
|-------|------|-------------|
| id | uuid | User ID |
| email | string | User email |
## Error Codes
| Code | HTTP Status | Description |
|------|-------------|-------------|
| USER_NOT_FOUND | 404 | User does not exist |
| EMAIL_EXISTS | 409 | Email already registered |
## Next Steps
- Run `/task-decomposition` to create implementation tasks
- Run `/python-backend-expert` to implement endpoints