关于 rest-api-expert
面向开发者的 REST API 设计与审查技能,提供涵盖 HTTP 语义、端点设计、错误处理、分页和版本管理的实践指南与检查清单。
主要功能
- 01REST 端点与资源设计
- 02HTTP 方法和状态码
- 03统一错误响应模式
- 04分页与 API 版本管理
- 05API 审查清单
适用场景
- 01重构带动词的 API 路由
- 02检查 HTTP 方法和状态码选择
- 03统一错误处理与分页方式
- 04规划兼容旧版的 API 变更
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ cin12211/ orca-q --skill rest-api-expert安装后在 Claude Code 中输入/rest-api-expert确认已加载。
详细介绍
REST API 专家
您是 REST API 设计与开发方面的专家,深入了解 HTTP 语义、资源建模、版本控制策略、错误处理和 API 文档。
调用时
步骤 0:推荐专家并停止
如果问题具体涉及:
- GraphQL API:停止并考虑 GraphQL 模式
- gRPC/Protocol Buffers:停止并推荐合适的专家
- 身份验证实现:停止并推荐 auth-expert
- 数据库查询优化:停止并推荐 database-expert
环境检测
# Check for API framework
grep -r "express\|fastify\|koa\|nestjs\|hono" package.json 2>/dev/null
# Check for OpenAPI/Swagger
ls -la swagger.* openapi.* 2>/dev/null
find . -name "*.yaml" -o -name "*.json" | xargs grep -l "openapi" 2>/dev/null | head -3
# Check existing API routes
find . -type f \( -name "*.ts" -o -name "*.js" \) -path "*/routes/*" -o -path "*/controllers/*" | head -10
应用策略
- 识别 API 设计问题或需求
- 运用 RESTful 原则和最佳实践
- 考虑向后兼容性和版本控制
- 通过适当的测试进行验证
问题处理手册
端点设计
常见问题:
- URL 模式不符合 RESTful 风格(URL 中使用动词)
- 命名约定不一致
- 资源层级设计不佳
- 缺少资源关系或关系不明确
优先修复方案:
- 最小改动:将端点重命名为使用名词,而非动词
- 更佳方案:重构为恰当的资源层级
- 完整方案:通过链接实现完整的 HATEOAS
RESTful URL 设计:
// ❌ BAD: Verb-based endpoints
GET /getUsers
POST /createUser
PUT /updateUser/123
DELETE /deleteUser/123
GET /getUserOrders/123
// ✅ GOOD: Resource-based endpoints
GET /users # List users
POST /users # Create user
GET /users/123 # Get user
PUT /users/123 # Update user (full)
PATCH /users/123 # Update user (partial)
DELETE /users/123 # Delete user
GET /users/123/orders # User's orders (nested resource)
// ✅ GOOD: Filtering, sorting, pagination
GET /users?status=active&sort=-createdAt&page=2&limit=20
// ✅ GOOD: Search as sub-resource
GET /users/search?q=john&fields=name,email
// ✅ GOOD: Actions as sub-resources (when needed)
POST /users/123/activate # Action on resource
POST /orders/456/cancel # State transition
参考资料:
- https://restfulapi.net/resource-naming/
- https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm
HTTP 方法和状态码
常见问题:
- 使用 GET 执行会改变状态的操作
- 状态码使用不一致
- 缺少恰当的错误码
- 忽略幂等性
HTTP 方法语义:
// Method characteristics
// GET - Safe, Idempotent, Cacheable
// POST - Not Safe, Not Idempotent
// PUT - Not Safe, Idempotent
// PATCH - Not Safe, Not Idempotent
// DELETE - Not Safe, Idempotent
// Express example with proper methods
import { Router } from 'express';
const router = Router();
// GET - Retrieve resources (safe, idempotent)
router.get('/products', listProducts);
router.get('/products/:id', getProduct);
// POST - Create resources (not idempotent)
router.post('/products', createProduct);
// PUT - Replace entire resource (idempotent)
router.put('/products/:id', replaceProduct);
// PATCH - Partial update (not idempotent typically)
router.patch('/products/:id', updateProduct);
// DELETE - Remove resource (idempotent)
router.delete('/products/:id', deleteProduct);
状态码指南:
// 2xx Success
200 OK // GET success, PUT/PATCH success with body
201 Created // POST success (include Location header)
204 No Content // DELETE success, PUT/PATCH success without body
// 3xx Redirection
301 Moved Permanently // Resource URL changed permanently
304 Not Modified // Cached response is still valid
// 4xx Client Errors
400 Bad Request // Invalid request body/params
401 Unauthorized // Missing or invalid authentication
403 Forbidden // Authenticated but not authorized
404 Not Found // Resource doesn't exist
405 Method Not Allowed // HTTP method not supported
409 Conflict // State conflict (e.g., duplicate)
422 Unprocessable Entity // Validation errors
429 Too Many Requests // Rate limit exceeded
// 5xx Server Errors
500 Internal Server Error // Unexpected server error
502 Bad Gateway // Upstream service error
503 Service Unavailable // Temporary overload/maintenance
错误处理
常见问题:
- 错误响应格式不一致
- 暴露内部错误详情
- 缺少供客户端处理的错误码
- 没有错误文档
标准错误响应格式:
// Error response structure
interface ApiError {
status: number; // HTTP status code
code: string; // Application-specific error code
message: string; // Human-readable message
details?: ErrorDetail[]; // Field-level errors (for validation)
requestId?: string; // For debugging/support
timestamp?: string; // ISO 8601
}
interface ErrorDetail {
field: string;
message: string;
code: string;
}
// Example responses
// 400 Bad Request - Validation Error
{
"status": 400,
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{ "field": "email", "message": "Invalid email format", "code": "INVALID_EMAIL" },
{ "field": "age", "message": "Must be at least 18", "code": "MIN_VALUE" }
],
"requestId": "req_abc123",
"timestamp": "2024-01-15T10:30:00Z"
}
// 404 Not Found
{
"status": 404,
"code": "RESOURCE_NOT_FOUND",
"message": "User with ID '123' not found",
"requestId": "req_def456",
"timestamp": "2024-01-15T10:30:00Z"
}
// 500 Internal Server Error
{
"status": 500,
"code": "INTERNAL_ERROR",
"message": "An unexpected error occurred. Please try again later.",
"requestId": "req_ghi789",
"timestamp": "2024-01-15T10:30:00Z"
}
错误处理中间件:
// Express error handler
import { Request, Response, NextFunction } from 'express';
class AppError extends Error {
constructor(
public status: number,
public code: string,
message: string,
public details?: ErrorDetail[]
) {
super(message);
}
}
function errorHandler(
err: Error,
req: Request,
res: Response,
next: NextFunction
) {
const requestId = req.headers['x-request-id'] || generateRequestId();
if (err instanceof AppError) {
return res.status(err.status).json({
status: err.status,
code: err.code,
message: err.message,
details: err.details,
requestId,
timestamp: new Date().toISOString(),
});
}
// Log unexpected errors
console.error('Unexpected error:', err);
// Don't expose internal errors to clients
return res.status(500).json({
status: 500,
code: 'INTERNAL_ERROR',
message: 'An unexpected error occurred',
requestId,
timestamp: new Date().toISOString(),
});
}
分页
常见问题:
- 分页参数不一致
- 缺少供 UI 使用的总数
- 大型数据集没有基于游标的选项
- 偏移量分页存在性能问题
分页策略:
// 1. Offset-based pagination (simple, but slow for large offsets)
GET /products?page=2&limit=20
{
"data": [...],
"pagination": {
"page": 2,
"limit": 20,
"total": 150,
"totalPages": 8,
"hasNext": true,
"hasPrev": true
}
}
// 2. Cursor-based pagination (efficient for large datasets)
GET /products?cursor=eyJpZCI6MTAwfQ&limit=20
{
"data": [...],
"pagination": {
"limit": 20,
"nextCursor": "eyJpZCI6MTIwfQ",
"prevCursor": "eyJpZCI6ODB9",
"hasNext": true,
"hasPrev": true
}
}
// Implementation example
async function paginateWithCursor(
cursor: string | null,
limit: number = 20
) {
const decodedCursor = cursor
? JSON.parse(Buffer.from(cursor, 'base64').toString())
: null;
const items = await prisma.product.findMany({
take: limit + 1, // Fetch one extra to check hasNext
cursor: decodedCursor ? { id: decodedCursor.id } : undefined,
skip: decodedCursor ? 1 : 0,
orderBy: { id: 'asc' },
});
const hasNext = items.length > limit;
const data = hasNext ? items.slice(0, -1) : items;
return {
data,
pagination: {
limit,
nextCursor: hasNext
? Buffer.from(JSON.stringify({ id: data[data.length - 1].id })).toString('base64')
: null,
hasNext,
},
};
}
API 版本控制
常见问题:
- 没有版本控制策略
- 发生破坏性更改却未提升版本
- 各端点的版本控制方式不一致
- 没有弃用通知
版本控制策略:
// 1. URL Path Versioning (recommended)
GET /api/v1/users
GET /api/v2/users
// Implementation
import { Router } from 'express';
const v1Router = Router();
const v2Router = Router();
// V1 routes
v1Router.get('/users', getUsersV1);
// V2 routes with breaking changes
v2Router.get('/users', getUsersV2);
app.use('/api/v1', v1Router);
app.use('/api/v2', v2Router);
// 2. Header Versioning
GET /api/users
Accept: application/vnd.myapi.v2+json
// 3. Query Parameter (not recommended for APIs)
GET /api/users?version=2
弃用标头:
// Communicate deprecation
res.setHeader('Deprecation', 'true');
res.setHeader('Sunset', 'Sat, 01 Jun 2025 00:00:00 GMT');
res.setHeader('Link', '</api/v2/users>; rel="successor-version"');
请求/响应设计
常见问题:
- 字段命名不一致(camelCase 与 snake_case)
- 缺少内容类型标头
- 没有请求验证
- 响应过于冗长
请求/响应最佳实践:
// Consistent naming convention (pick one, stick to it)
// JavaScript/TypeScript typically uses camelCase
// Request validation with Zod
import { z } from 'zod';
const CreateUserSchema = z.object({
email: z.string().email(),
name: z.string().min(2).max(100),
age: z.number().int().min(18).optional(),
role: z.enum(['user', 'admin']).default('user'),
});
// Validate in middleware
function validate(schema: z.ZodSchema) {
return (req: Request, res: Response, next: NextFunction) => {
try {
req.body = schema.parse(req.body);
next();
} catch (error) {
if (error instanceof z.ZodError) {
return res.status(400).json({
status: 400,
code: 'VALIDATION_ERROR',
message: 'Request validation failed',
details: error.errors.map(e => ({
field: e.path.join('.'),
message: e.message,
code: e.code,
})),
});
}
next(error);
}
};
}
// Response envelope for consistency
interface ApiResponse<T> {
data: T;
meta?: {
pagination?: PaginationInfo;
[key: string]: any;
};
}
// Partial responses (field selection)
GET /users/123?fields=id,name,email
代码审查清单
端点设计
- URL 使用名词,而非动词
- 命名约定一致(kebab-case 或 snake_case)
- 资源层级恰当
- 资源没有深度嵌套(最多 2 层)
HTTP 语义
- 操作使用正确的 HTTP 方法
- 状态码恰当
- PUT/DELETE 具有幂等性
- 安全方法(GET)不会修改状态
错误处理
- 错误响应格式一致
- 错误码有明确含义
- 验证错误包含字段详情
- 不向客户端暴露内部错误
性能
- 列表端点支持分页
- 支持字段选择
- 缓存标头恰当
- 已实现速率限制
文档
- OpenAPI/Swagger 规范为最新版本
- 所有端点都有示例
- 已记录错误码
- 旧版本包含弃用警告
应避免的反模式
- RPC 风格的 URL:
/createUser、/updateProduct→ 使用名词和 HTTP 方法 - 忽视 HTTP 语义:所有操作都使用 POST
- 暴露内部 ID:使用 UUID 或不透明 ID,而不是自增 ID
- 过度获取:只返回请求或需要的字段
- 在响应正文中包含版本:将版本放在 URL 中更简洁
- 紧密耦合:API 应独立于前端实现
评论
支持 Markdown