关于 api-design
api-design 是一项用于设计一致、易维护 REST API 的技能,面向 API 开发者,涵盖端点规范、错误处理、分页、版本管理与文档,并提供主题参考文件。
主要功能
- 01REST 资源与 HTTP 方法规范
- 02统一错误响应格式
- 03游标与偏移分页
- 04筛选、排序与速率限制
- 05API 版本管理与文档指南
适用场景
- 01为新 API 设计端点
- 02统一错误与分页行为
- 03规划破坏性变更和 API 版本
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ scientiacapital/ skills --skill api-design安装后在 Claude Code 中输入/api-design确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
config.jsonreference/checklist.mddocumentation.mderror-handling.mdfiltering-sorting.mdpagination.mdrate-limiting.mdrest-patterns.mdversioning.md详细介绍
优秀的 API 设计能让正确的做法变得简单,让错误的做法变得困难。这项技能可帮助你创建易于使用和维护的 API。
<quick_start>
资源命名: 使用名词、复数、小写字母和连字符(/users、/blog-posts)
HTTP 方法: GET(读取)、POST(创建)、PUT(替换)、PATCH(更新)、DELETE(移除)
状态码: 200 OK、201 Created、204 No Content、400 Bad Request、401 Unauthorized、403 Forbidden、404 Not Found、429 Rate Limited
分页: 实时数据优先使用基于游标的分页;使用 ?limit=20&cursor=xxx
版本控制: URL 路径(/api/v1/)是最清晰的方式
</quick_start>
<success_criteria> API 设计成功的标准:
- 资源使用复数名词,且命名一致
- HTTP 方法符合语义(GET=读取、POST=创建等)
- 错误响应格式统一,包含 code、message、details、requestId
- 已实现分页(基于游标或偏移量)
- 包含速率限制标头(X-RateLimit-Limit、Remaining、Reset)
- 在需要进行破坏性变更之前定义版本控制策略 </success_criteria>
<core_principles>
API 设计原则
- 一致性 - 各处采用相同模式(命名、错误、分页)
- 可预测性 - 开发者能够推测功能的工作方式
- 简洁性 - 简单场景应易于处理,同时支持复杂场景
- 向后兼容性 - 不破坏现有客户端
- 自说明性 - 命名清晰,错误消息有帮助 </core_principles>
<rest_basics>
RESTful 约定
资源命名
# GOOD: Nouns, plural, lowercase, hyphenated
GET /users
GET /users/{id}
GET /users/{id}/posts
GET /blog-posts
GET /api/v1/user-preferences
# BAD: Verbs, singular, mixed case, underscores
GET /getUser
GET /user/{id}
GET /User/{id}/getPosts
GET /blog_posts
HTTP 方法
| 方法 | 用途 | 幂等 | 安全 | 请求体 |
|---|---|---|---|---|
| GET | 读取资源 | 是 | 是 | 否 |
| POST | 创建资源 | 否 | 否 | 是 |
| PUT | 替换资源 | 是 | 否 | 是 |
| PATCH | 部分更新 | 否* | 否 | 是 |
| DELETE | 移除资源 | 是 | 否 | 否 |
*如果应用相同的补丁,PATCH 就是幂等的。
CRUD 操作
# Collection operations
GET /users # List all users
POST /users # Create a user
# Single resource operations
GET /users/{id} # Get one user
PUT /users/{id} # Replace user
PATCH /users/{id} # Update user fields
DELETE /users/{id} # Delete user
# Nested resources
GET /users/{id}/posts # User's posts
POST /users/{id}/posts # Create post for user
GET /posts/{id}/comments # Post's comments
操作(非 CRUD 操作)
# When you need actions, use verbs as sub-resources
POST /users/{id}/activate
POST /users/{id}/deactivate
POST /orders/{id}/cancel
POST /invoices/{id}/send
POST /auth/login
POST /auth/logout
POST /auth/refresh
状态码
| 代码 | 含义 | 使用时机 |
|---|---|---|
| 200 | OK | GET、PUT、PATCH 成功 |
| 201 | Created | 成功创建资源的 POST 请求 |
| 204 | No Content | DELETE 成功 |
| 400 | Bad Request | 输入无效、验证错误 |
| 401 | Unauthorized | 缺少或无效的身份验证 |
| 403 | Forbidden | 已通过身份验证,但未获授权 |
| 404 | Not Found | 资源不存在 |
| 409 | Conflict | 重复、状态冲突 |
| 422 | Unprocessable | 验证失败(可替代 400) |
| 429 | Too Many Requests | 请求过多,已触发速率限制 |
| 500 | Server Error | 意外的服务器错误 |
| </rest_basics> |
<error_handling>
错误响应
统一的错误格式
// Standard error response
interface ErrorResponse {
error: {
code: string; // Machine-readable code
message: string; // Human-readable message
details?: ErrorDetail[]; // Field-level errors
requestId?: string; // For support/debugging
};
}
interface ErrorDetail {
field: string;
message: string;
code: string;
}
示例
// 400 Bad Request - Validation error
{
"error": {
"code": "validation_error",
"message": "Invalid request parameters",
"details": [
{ "field": "email", "message": "Invalid email format", "code": "invalid_format" },
{ "field": "age", "message": "Must be at least 13", "code": "min_value" }
],
"requestId": "req_abc123"
}
}
// 401 Unauthorized
{
"error": {
"code": "unauthorized",
"message": "Invalid or expired authentication token",
"requestId": "req_abc123"
}
}
// 403 Forbidden
{
"error": {
"code": "forbidden",
"message": "You don't have permission to access this resource",
"requestId": "req_abc123"
}
}
// 404 Not Found
{
"error": {
"code": "not_found",
"message": "User not found",
"requestId": "req_abc123"
}
}
// 429 Rate Limited
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Please retry after 60 seconds",
"requestId": "req_abc123"
}
}
实现
// Error class
class ApiError extends Error {
constructor(
public statusCode: number,
public code: string,
message: string,
public details?: ErrorDetail[]
) {
super(message);
}
}
// Error handler middleware
function errorHandler(error: Error, req: Request, res: Response) {
const requestId = req.headers['x-request-id'] || crypto.randomUUID();
if (error instanceof ApiError) {
return res.status(error.statusCode).json({
error: {
code: error.code,
message: error.message,
details: error.details,
requestId,
},
});
}
// Log unexpected errors
logger.error('Unexpected error', { error, requestId });
// Don't expose internal details
return res.status(500).json({
error: {
code: 'internal_error',
message: 'An unexpected error occurred',
requestId,
},
});
}
</error_handling>
## 分页基于游标(推荐)
// Request
GET /posts?limit=20&cursor=eyJpZCI6MTAwfQ
// Response
{
"data": [...],
"pagination": {
"hasMore": true,
"nextCursor": "eyJpZCI6MTIwfQ",
"prevCursor": "eyJpZCI6MTAwfQ"
}
}
优点: 结果一致,可处理实时数据 缺点: 无法跳转到第 N 页
基于偏移量
// Request
GET /posts?page=2&limit=20
// or
GET /posts?offset=20&limit=20
// Response
{
"data": [...],
"pagination": {
"page": 2,
"limit": 20,
"total": 150,
"totalPages": 8
}
}
优点: 可跳转到任意页 缺点: 实时数据下结果不一致,在大型表上速度较慢
实现(游标)
// Encode/decode cursor
function encodeCursor(data: object): string {
return Buffer.from(JSON.stringify(data)).toString('base64url');
}
function decodeCursor(cursor: string): object {
return JSON.parse(Buffer.from(cursor, 'base64url').toString());
}
// Query with cursor
async function getPosts(limit: number, cursor?: string) {
const where: any = {};
if (cursor) {
const { id } = decodeCursor(cursor);
where.id = { lt: id };
}
const posts = await db.post.findMany({
where,
orderBy: { id: 'desc' },
take: limit + 1, // Fetch one extra to check hasMore
});
const hasMore = posts.length > limit;
const data = hasMore ? posts.slice(0, -1) : posts;
return {
data,
pagination: {
hasMore,
nextCursor: hasMore ? encodeCursor({ id: data[data.length - 1].id }) : null,
},
};
}
## 筛选与排序
使用查询参数:?status=active、?sort=-created_at(前缀 - 表示降序)、?fields=id,name,email。使用 Zod schema 验证,并根据解析后的筛选条件构建 Prisma where 子句。
完整的查询参数模式和实现方式,请参阅 reference/filtering-sorting.md。
URL 路径版本控制(推荐)
GET /api/v1/users
GET /api/v2/users
优点: 清晰、易于理解 缺点: 需要更多维护
标头版本控制
GET /api/users
Accept: application/vnd.api+json; version=2
优点: URL 简洁 缺点: 不明显,测试较困难
### 何时创建新版本在以下情况下创建新版本:
- 从响应中移除字段
- 更改字段类型或格式
- 移除端点
- 更改身份验证
在以下情况下无需创建新版本:
- 添加新的可选字段
- 添加新的端点
- 添加新的可选参数
- 修复错误
<rate_limiting>
速率限制
包含以下标头:X-RateLimit-Limit、X-RateLimit-Remaining、X-RateLimit-Reset。超出限制时返回 429,并附带 Retry-After。使用滑动窗口算法(例如 @upstash/ratelimit)。
层级定义和实现请参阅 reference/rate-limiting.md。
</rate_limiting>
| 主题 | 参考文件 | 加载时机 |
|---|---|---|
| REST 模式 | reference/rest-patterns.md | 设计端点时 |
| 错误处理 | reference/error-handling.md | 编写错误响应时 |
| 分页 | reference/pagination.md | 设计列表端点时 |
| 筛选与排序 | reference/filtering-sorting.md | 设计查询参数时 |
| 速率限制 | reference/rate-limiting.md | 配置限流时 |
| 版本控制 | reference/versioning.md | 演进 API 时 |
| 文档 | reference/documentation.md | 编写 OpenAPI 规范和文档时 |
| 检查清单 | reference/checklist.md | 发布前验证时 |
加载方式: 请求特定主题,或检查上下文是否有所提示。
写入结果旁路文件
最后一步,将内容写入 ~/.claude/skill-analytics/last-outcome-api-design.json:
{"ts":"[UTC ISO8601]","skill":"api-design","version":"1.0.0","variant":"default",
"status":"[success|partial|error]","runtime_ms":[estimated ms from start],
"metrics":{"endpoints_designed":[n],"patterns_applied":[n]},
"error":null,"session_id":"[YYYY-MM-DD]"}
如果某些阶段失败但已生成结果,请使用状态 "partial"。只有在未生成任何输出时才使用 "error"。