name: api-design 描述:遵循行业最佳实践设计清洁、可扩展和维护的REST和GraphQL API。用于设计公共或内部API、规划端点结构、定义请求/响应合约、建立版本控制策略、实现认证模式、设计数据模型、创建API文档、确保一致的错误处理、优化性能或建立微服务之间的服务合约。
API 设计 - 构建清洁、可扩展的 REST & GraphQL API
何时使用此技能
- 从零开始设计新的REST或GraphQL API
- 规划端点结构和URL模式
- 定义请求/响应合约和数据模式
- 建立API版本控制和弃用策略
- 实现认证和授权模式
- 创建API文档(OpenAPI/Swagger)
- 设计错误响应格式和状态码
- 规划分页、过滤和排序策略
- 建立速率限制和节流策略
- 设计Webhooks或事件驱动集成
- 创建微服务的服务合约
- 优化API性能和缓存策略
何时使用此技能
- 设计公共或内部API,规划端点,定义服务之间的合约。
- 当处理相关任务或功能时
- 在需要此专业知识的开发期间
使用时机:设计公共或内部API,规划端点,定义服务之间的合约。
核心原则
- 一致性 - 跨端点的可预测模式
- 简洁性 - 易于理解和使用
- 版本控制 - 支持进化而不破坏客户端
- 安全性 - 认证、授权、速率限制
- 文档化 - 清晰、最新的API规范
REST API 设计
1. 基于资源的URLs
✅ 好 - 使用名词,而非动词
GET /users - 列出用户
GET /users/:id - 获取特定用户
POST /users - 创建用户
PUT /users/:id - 更新用户(完全替换)
PATCH /users/:id - 更新用户(部分)
DELETE /users/:id - 删除用户
GET /users/:id/posts - 获取用户的帖子
POST /users/:id/posts - 为用户创建帖子
❌ 不好 - URL中使用动词
GET /getUsers
POST /createUser
POST /users/delete/:id
2. HTTP 方法与状态码
// ✅ 正确的HTTP方法使用
app.get('/users', async (req, res) => {
const users = await db.users.findAll();
res.json(users); // 200 OK
});
app.post('/users', async (req, res) => {
const user = await db.users.create(req.body);
res.status(201) // 201 已创建
.location(`/users/${user.id}`)
.json(user);
});
app.put('/users/:id', async (req, res) => {
const user = await db.users.update(req.params.id, req.body);
if (!user) {
return res.status(404).json({ error: '用户未找到' });
}
res.json(user); // 200 OK
});
app.delete('/users/:id', async (req, res) => {
await db.users.delete(req.params.id);
res.status(204).send(); // 204 无内容
});
// ✅ 常见状态码
// 2xx 成功
200 OK - 请求成功
201 Created - 资源已创建
204 No Content - 成功,无响应体
// 4xx 客户端错误
400 Bad Request - 无效输入
401 Unauthorized - 未认证
403 Forbidden - 已认证但无权限
404 Not Found - 资源不存在
409 Conflict - 重复或冲突状态
422 Unprocessable - 验证失败
429 Too Many Requests - 速率限制
// 5xx 服务器错误
500 Internal Error - 服务器内部错误
503 Service Unavailable - 暂时不可用
3. 请求/响应格式
// ✅ 一致的响应结构
interface ApiResponse<T> {
data: T;
meta?: {
page?: number;
limit?: number;
total?: number;
};
links?: {
self: string;
next?: string;
prev?: string;
};
}
// 成功响应
{
"data": {
"id": "123",
"name": "John Doe",
"email": "john@example.com"
}
}
// 列表响应与分页
{
"data": [
{ "id": "1", "name": "用户 1" },
{ "id": "2", "name": "用户 2" }
],
"meta": {
"page": 1,
"limit": 20,
"total": 100
},
"links": {
"self": "/users?page=1",
"next": "/users?page=2"
}
}
// 错误响应
{
"error": {
"code": "VALIDATION_ERROR",
"message": "邮箱格式无效",
"details": [
{
"field": "email",
"message": "必须为有效邮箱"
}
]
}
}
4. 过滤、排序、分页
// ✅ 查询参数用于过滤
GET /users?status=active&role=admin
GET /posts?author=john&tags=tech,programming
GET /products?minPrice=10&maxPrice=100
app.get('/users', async (req, res) => {
const { status, role, page = 1, limit = 20, sort = 'createdAt' } = req.query;
const query = {};
if (status) query.status = status;
if (role) query.role = role;
const users = await db.users.findMany({
where: query,
skip: (page - 1) * limit,
take: limit,
orderBy: { [sort]: 'desc' }
});
const total = await db.users.count({ where: query });
res.json({
data: users,
meta: { page, limit, total },
links: {
self: `/users?page=${page}`,
next: page * limit < total ? `/users?page=${page + 1}` : null
}
});
});
// ✅ 排序
GET /users?sort=name - 按名称升序排序
GET /users?sort=-createdAt - 按创建时间降序排序
GET /users?sort=role,-createdAt - 多字段排序
// ✅ 字段选择(稀疏字段集)
GET /users?fields=id,name,email - 仅返回指定字段
5. 版本控制
// ✅ URL版本控制(最常见)
GET /api/v1/users
GET /api/v2/users
app.use('/api/v1', v1Router);
app.use('/api/v2', v2Router);
// ✅ 头部版本控制
GET /api/users
Headers: { "Accept-Version": "v2" }
// ✅ 弃用警告
app.use('/api/v1', (req, res, next) => {
res.set('X-API-Deprecation', 'v1 将于 2024-12-31 弃用');
res.set('X-API-Upgrade', '查看 /api/v2 获取最新版本');
next();
});
6. 认证与授权
// ✅ Bearer 令牌认证
GET /api/users
Headers: { "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." }
function authenticate(req, res, next) {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: '未提供令牌' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
res.status(401).json({ error: '无效令牌' });
}
}
// ✅ 速率限制
import rateLimit from 'express-rate-limit';
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 分钟
max: 100
});
app.use('/api/', limiter);
// ✅ 服务间 API 密钥
GET /api/users
Headers: { "X-API-Key": "sk_live_abc123..." }
function requireApiKey(req, res, next) {
const apiKey = req.get('X-API-Key');
if (!isValidApiKey(apiKey)) {
return res.status(401).json({ error: '无效 API 密钥' });
}
next();
}
7. HATEOAS(超媒体)
// ✅ 包含相关资源链接
{
"data": {
"id": "123",
"name": "John Doe",
"email": "john@example.com"
},
"links": {
"self": "/users/123",
"posts": "/users/123/posts",
"followers": "/users/123/followers"
}
}
// ✅ 状态转换的操作链接
{
"data": {
"id": "456",
"status": "pending",
"amount": 100
},
"actions": {
"approve": {
"method": "POST",
"href": "/orders/456/approve"
},
"cancel": {
"method": "DELETE",
"href": "/orders/456"
}
}
}
GraphQL API 设计
1. 模式定义
# ✅ 清晰、类型化的模式
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
createdAt: DateTime!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
published: Boolean!
}
type Query {
user(id: ID!): User
users(limit: Int, offset: Int): [User!]!
post(id: ID!): Post
posts(authorId: ID, published: Boolean): [Post!]!
}
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User!
deleteUser(id: ID!): Boolean!
createPost(input: CreatePostInput!): Post!
publishPost(id: ID!): Post!
}
input CreateUserInput {
name: String!
email: String!
}
input UpdateUserInput {
name: String
email: String
}
input CreatePostInput {
title: String!
content: String!
authorId: ID!
}
2. 解析器
// ✅ 高效的解析器与 DataLoader
import DataLoader from 'dataloader';
const userLoader = new DataLoader(async (ids) => {
const users = await db.users.findMany({
where: { id: { in: ids } }
});
// 按输入 ID 顺序返回
return ids.map(id => users.find(u => u.id === id));
});
const resolvers = {
Query: {
user: async (_, { id }) => {
return await userLoader.load(id);
},
users: async (_, { limit = 20, offset = 0 }) => {
return await db.users.findMany({
take: limit,
skip: offset
});
}
},
Mutation: {
createUser: async (_, { input }) => {
return await db.users.create(input);
},
updateUser: async (_, { id, input }) => {
return await db.users.update(id, input);
}
},
User: {
// User.posts 字段的解析器
posts: async (user) => {
return await db.posts.findMany({
where: { authorId: user.id }
});
}
}
};
3. 错误处理
// ✅ GraphQL 错误处理
import { GraphQLError } from 'graphql';
const resolvers = {
Query: {
user: async (_, { id }) => {
const user = await db.users.findById(id);
if (!user) {
throw new GraphQLError('用户未找到', {
extensions: {
code: 'USER_NOT_FOUND',
id
}
});
}
return user;
}
},
Mutation: {
createUser: async (_, { input }) => {
try {
return await db.users.create(input);
} catch (error) {
if (error.code === 'P2002') { // 唯一约束
throw new GraphQLError('邮箱已存在', {
extensions: {
code: 'DUPLICATE_EMAIL',
field: 'email'
}
});
}
throw error;
}
}
}
};
API 文档
1. OpenAPI/Swagger (REST)
// ✅ OpenAPI 规范
/**
* @swagger
* /users:
* get:
* summary: 列出所有用户
* tags: [Users]
* parameters:
* - in: query
* name: page
* schema:
* type: integer
* description: 页码
* responses:
* 200:
* description: 成功
* content:
* application/json:
* schema:
* type: object
* properties:
* data:
* type: array
* items:
* $ref: '#/components/schemas/User'
*/
app.get('/users', async (req, res) => {
// 实现
});
// components/schemas/User
/**
* @swagger
* components:
* schemas:
* User:
* type: object
* required:
* - id
* - email
* properties:
* id:
* type: string
* example: "123"
* name:
* type: string
* example: "John Doe"
* email:
* type: string
* format: email
* example: "john@example.com"
*/
2. GraphQL 文档
# ✅ 模式中的描述(自动文档化)
"""
表示系统中的用户
"""
type User {
"""唯一标识符"""
id: ID!
"""用户全名"""
name: String!
"""用户邮箱地址"""
email: String!
"""此用户创作的帖子"""
posts: [Post!]!
}
"""
创建新用户
"""
createUser(
"""用户详情"""
input: CreateUserInput!
): User!
API 最佳实践
1. 幂等性
// ✅ 幂等操作(可安全重试)
app.put('/users/:id', async (req, res) => {
// PUT 是幂等的 - 重复结果相同
const user = await db.users.upsert({
where: { id: req.params.id },
create: req.body,
update: req.body
});
res.json(user);
});
// ✅ POST 的幂等键
app.post('/payments', async (req, res) => {
const idempotencyKey = req.get('Idempotency-Key');
if (!idempotencyKey) {
return res.status(400).json({ error: '需要 Idempotency-Key' });
}
// 检查是否已处理
const existing = await cache.get(`payment:${idempotencyKey}`);
if (existing) {
return res.json(existing); // 返回缓存结果
}
const payment = await processPayment(req.body);
await cache.set(`payment:${idempotencyKey}`, payment, 24 * 60 * 60);
res.status(201).json(payment);
});
2. 缓存
// ✅ HTTP 缓存头部
app.get('/products/:id', async (req, res) => {
const product = await db.products.findById(req.params.id);
// 从内容生成 ETag
const etag = generateETag(product);
// 检查客户端是否有当前版本
if (req.get('If-None-Match') === etag) {
return res.status(304).send(); // 未修改
}
res.set({
'ETag': etag,
'Cache-Control': 'public, max-age=300', // 5 分钟
'Last-Modified': product.updatedAt.toUTCString()
});
res.json(product);
});
3. Webhooks
// ✅ 用于异步事件的 Webhook 系统
interface WebhookPayload {
event: string;
data: any;
timestamp: string;
signature: string; // 用于验证的 HMAC
}
async function sendWebhook(url: string, event: string, data: any) {
const payload = {
event,
data,
timestamp: new Date().toISOString()
};
// 签名负载
const signature = crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(JSON.stringify(payload))
.digest('hex');
await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Webhook-Signature': signature
},
body: JSON.stringify({ ...payload, signature })
});
}
// 在事件上触发 webhooks
app.post('/users', async (req, res) => {
const user = await db.users.create(req.body);
// 异步发送 webhook
sendWebhook(
'https://customer.com/webhooks',
'user.created',
user
).catch(console.error);
res.status(201).json(user);
});
API 设计检查清单
REST:
□ 基于资源的 URLs(名词,非动词)
□ 正确的 HTTP 方法和状态码
□ 一致的响应格式
□ 列表的分页
□ 过滤、排序、字段选择
□ API 版本控制策略
□ 认证(Bearer 令牌、API 密钥)
□ 配置速率限制
□ 正确配置 CORS
□ 标准化的错误响应
GraphQL:
□ 清晰、类型化的模式
□ 高效的解析器(DataLoader)
□ 实现分页(游标或偏移)
□ 带代码的错误处理
□ 认证与授权
□ 查询复杂度限制
□ 深度限制
□ 生产中禁用自省
文档:
□ 发布 OpenAPI/GraphQL 模式
□ 示例请求/响应
□ 认证文档
□ 错误代码文档化
□ 维护变更日志
□ 重大更改的迁移指南
性能:
□ 优化的数据库查询
□ 消除 N+1 查询
□ 响应缓存
□ 启用压缩(gzip)
□ 静态响应的 CDN
安全:
□ 所有端点的输入验证
□ 预防 SQL 注入
□ 每个端点的速率限制
□ 支持 API 密钥轮换
□ 敏感操作的审计日志
资源
记住:优秀的 API 是可预测、文档完善且易于使用的。为 API 消费者设计,而不仅仅是实现。