关于 rest-api
面向 REST API 设计与运维的开发参考技能,涵盖资源约定、状态码,以及验证、分页和安全等生产实践。
主要功能
- 01资源命名与 HTTP 方法
- 02状态码和响应格式
- 03查询参数与分页
- 04验证、安全和 API 版本管理
适用场景
- 01设计一致的 REST 端点
- 02规划分页和 API 版本
- 03诊断 HTTP 错误及 API 问题
MCP 相关
该技能提及可使用 MCP 文档工具获取更全面的 REST API 文档。
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ claude-dev-suite/ claude-dev-suite --skill rest-api安装后在 Claude Code 中输入/rest-api确认已加载。
附带文件quick-ref/conventions.mdquick-ref/error-handling.md
详细介绍
REST API 核心知识
深入了解:使用
mcp__documentation__fetch_docs,并指定技术:rest-api,获取完整文档。
资源命名
# Collections (plural nouns)
GET /users # List users
POST /users # Create user
GET /users/{id} # Get user
PUT /users/{id} # Update user
DELETE /users/{id} # Delete user
# Nested resources
GET /users/{id}/posts # User's posts
POST /users/{id}/posts # Create user's post
# Avoid verbs in URLs
❌ GET /getUsers
❌ POST /createUser
✅ GET /users
✅ POST /users
HTTP 方法
| 方法 | 用途 | 幂等 |
|---|---|---|
| GET | 读取资源 | 是 |
| POST | 创建资源 | 否 |
| PUT | 替换资源 | 是 |
| PATCH | 部分更新 | 是 |
| DELETE | 删除资源 | 是 |
状态码
| 代码 | 含义 | 使用场景 |
|---|---|---|
| 200 | 成功 | GET/PUT/PATCH 成功 |
| 201 | 已创建 | POST 成功 |
| 204 | 无内容 | DELETE 成功 |
| 400 | 错误请求 | 验证错误 |
| 401 | 未授权 | 缺少或无效的身份验证信息 |
| 403 | 禁止访问 | 无权限 |
| 404 | 未找到 | 资源不存在 |
| 409 | 冲突 | 重复或冲突 |
| 422 | 无法处理 | 语义错误 |
| 500 | 服务器错误 | 内部错误 |
响应格式
// Success
{
"data": { "id": 1, "name": "John" },
"meta": { "timestamp": "2024-01-15T10:00:00Z" }
}
// Collection
{
"data": [...],
"meta": { "total": 100, "page": 1, "limit": 20 }
}
// Error
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Email is required",
"details": [{ "field": "email", "message": "Required" }]
}
}
查询参数
GET /users?status=active&sort=-createdAt&page=1&limit=20
GET /users?fields=id,name,email
GET /users?include=posts,profile
GET /users?filter[age][gte]=18
不适用此技能的场景
- GraphQL API 设计(使用
graphql技能) - tRPC 类型安全 API(使用
trpc技能) - OpenAPI 规范编写(使用
openapi技能) - 需要 WebSockets 或 SSE 的实时 API
- 需要复杂嵌套查询的 API(可考虑 GraphQL)
反模式
| 反模式 | 问题 | 解决方案 |
|---|---|---|
在 URL 中使用动词(/getUser、/createOrder) | 不符合 REST 风格,违反资源命名规则 | 对名词使用 HTTP 方法(GET /users、POST /orders) |
| 错误时返回 200 | 具有误导性,破坏 HTTP 语义 | 使用适当的 4xx/5xx 状态码 |
| 不对 API 进行版本管理 | 破坏性变更会影响所有客户端 | 使用 URL 或请求头版本管理(/v1/、/v2/) |
| 直接暴露数据库 ID | 存在安全风险,泄露实现细节 | 使用 UUID 或不透明标识符 |
| 大型集合不进行分页 | 性能问题、超时 | 实现游标分页或偏移分页 |
| 忽略 HTTP 缓存头 | 性能较差,产生不必要的负载 | 使用 ETag、Cache-Control、Last-Modified |
| 对会改变状态的操作使用 GET | 存在安全风险,违反 REST 原则 | 使用 POST、PUT、PATCH、DELETE |
| 响应格式不一致 | 令客户端困惑,导致集成问题 | 统一使用 JSON 信封格式 |
| 未设置速率限制 | API 滥用、资源耗尽 | 实现速率限制,并返回 429 响应 |
快速故障排查
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 401 Unauthorized | 缺少或无效的身份验证令牌 | 检查 Authorization 请求头,验证令牌 |
| 403 Forbidden | 身份验证有效,但权限不足 | 检查用户角色,验证访问控制 |
| 404 Not Found | 资源不存在或路径错误 | 验证端点 URL,检查资源 ID |
| 409 Conflict | 资源已存在或状态冲突 | 检查唯一性约束,处理幂等性 |
| 422 Unprocessable Entity | 验证失败 | 检查请求正文,依据架构进行验证 |
| 429 Too Many Requests | 超出速率限制 | 实现退避机制,检查速率限制请求头 |
| 500 Internal Server Error | 服务端错误或崩溃 | 检查服务器日志,添加错误处理 |
| CORS 错误 | 缺少 CORS 请求头 | 配置 CORS 中间件并设置允许的来源 |
| API 响应缓慢 | 未分页、N+1 查询 | 添加分页,优化数据库查询 |
生产环境就绪
安全配置
// Input validation and sanitization
import { z } from 'zod';
const CreateUserSchema = z.object({
name: z.string().min(1).max(100).trim(),
email: z.string().email().toLowerCase(),
age: z.number().int().min(0).max(150).optional(),
});
// Validate request body
app.post('/users', async (req, res) => {
const result = CreateUserSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({
error: { code: 'VALIDATION_ERROR', details: result.error.issues },
});
}
// Use result.data (validated and transformed)
});
// SQL injection prevention - always use parameterized queries
// NEVER: `SELECT * FROM users WHERE id = '${userId}'`
// GOOD: Use ORM or prepared statements
速率限制
import rateLimit from 'express-rate-limit';
// Different limits for different endpoints
const standardLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100,
standardHeaders: true,
message: { error: { code: 'RATE_LIMIT_EXCEEDED' } },
});
const authLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1 hour
max: 5, // Strict for auth endpoints
skipSuccessfulRequests: true,
});
app.use('/api', standardLimiter);
app.use('/api/auth/login', authLimiter);
API 版本管理
// URL versioning (recommended for breaking changes)
app.use('/api/v1', v1Router);
app.use('/api/v2', v2Router);
// Header versioning (for minor versions)
app.use('/api', (req, res, next) => {
const version = req.headers['api-version'] || '1.0';
req.apiVersion = version;
next();
});
// Sunset header for deprecated endpoints
app.get('/api/v1/legacy', (req, res) => {
res.set('Sunset', 'Sat, 01 Jun 2025 00:00:00 GMT');
res.set('Deprecation', 'true');
// ... handle request
});
分页
// Cursor-based pagination (recommended for large datasets)
interface PaginatedResponse<T> {
data: T[];
meta: {
nextCursor: string | null;
hasMore: boolean;
};
}
app.get('/users', async (req, res) => {
const cursor = req.query.cursor as string | undefined;
const limit = Math.min(parseInt(req.query.limit as string) || 20, 100);
const users = await db.users.findMany({
take: limit + 1, // Fetch one extra to check if there's more
cursor: cursor ? { id: cursor } : undefined,
orderBy: { createdAt: 'desc' },
});
const hasMore = users.length > limit;
const data = hasMore ? users.slice(0, -1) : users;
res.json({
data,
meta: {
nextCursor: hasMore ? data[data.length - 1].id : null,
hasMore,
},
});
});
错误处理
// Consistent error response format
interface APIError {
error: {
code: string;
message: string;
details?: unknown;
requestId?: string;
};
}
// Global error handler
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
const requestId = req.headers['x-request-id'] as string;
// Log error with context
logger.error({
err,
requestId,
path: req.path,
method: req.method,
});
// Don't leak internal errors
const statusCode = err instanceof HTTPError ? err.statusCode : 500;
const message = statusCode === 500 ? 'Internal Server Error' : err.message;
res.status(statusCode).json({
error: {
code: err.code || 'INTERNAL_ERROR',
message,
requestId,
},
});
});
监控指标
| 指标 | 告警阈值 |
|---|---|
| 请求延迟 p99 | > 500ms |
| 错误率(4xx) | > 5% |
| 错误率(5xx) | > 1% |
| 速率限制触发次数 | > 100/min |
| 请求大小 | > 10MB |
CORS 配置
import cors from 'cors';
const corsOptions = {
origin: (origin, callback) => {
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') || [];
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Request-ID'],
credentials: true,
maxAge: 86400, // 24 hours
};
app.use(cors(corsOptions));
请求/响应日志记录
import pino from 'pino-http';
app.use(pino({
redact: ['req.headers.authorization', 'req.body.password'],
serializers: {
req: (req) => ({
method: req.method,
url: req.url,
query: req.query,
}),
res: (res) => ({
statusCode: res.statusCode,
}),
},
}));
检查清单
- 所有端点均进行输入验证
- 已配置速率限制
- 已正确限制 CORS
- API 版本控制策略
- 基于游标的分页
- 统一的错误格式
- 请求 ID 跟踪
- 结构化日志记录
- URL 中不包含敏感数据
- 已配置请求正文大小限制
- 超时处理
- 健康检查端点
参考文档
评论
支持 Markdown