mcpField
R
rest-api-expert

rest-api-expert

github.com/cin12211开发22535 次安装2 天前更新
代码仓库

关于 rest-api-expert

面向开发者的 REST API 设计与审查技能,提供涵盖 HTTP 语义、端点设计、错误处理、分页和版本管理的实践指南与检查清单。

主要功能

  1. 01REST 端点与资源设计
  2. 02HTTP 方法和状态码
  3. 03统一错误响应模式
  4. 04分页与 API 版本管理
  5. 05API 审查清单

适用场景

  1. 01重构带动词的 API 路由
  2. 02检查 HTTP 方法和状态码选择
  3. 03统一错误处理与分页方式
  4. 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
应用策略
  1. 识别 API 设计问题或需求
  2. 运用 RESTful 原则和最佳实践
  3. 考虑向后兼容性和版本控制
  4. 通过适当的测试进行验证

问题处理手册

端点设计

常见问题:

  • URL 模式不符合 RESTful 风格(URL 中使用动词)
  • 命名约定不一致
  • 资源层级设计不佳
  • 缺少资源关系或关系不明确

优先修复方案:

  1. 最小改动:将端点重命名为使用名词,而非动词
  2. 更佳方案:重构为恰当的资源层级
  3. 完整方案:通过链接实现完整的 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

参考资料:

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 规范为最新版本
  • 所有端点都有示例
  • 已记录错误码
  • 旧版本包含弃用警告

应避免的反模式

  1. RPC 风格的 URL:/createUser、/updateProduct → 使用名词和 HTTP 方法
  2. 忽视 HTTP 语义:所有操作都使用 POST
  3. 暴露内部 ID:使用 UUID 或不透明 ID,而不是自增 ID
  4. 过度获取:只返回请求或需要的字段
  5. 在响应正文中包含版本:将版本放在 URL 中更简洁
  6. 紧密耦合:API 应独立于前端实现

评论

支持 Markdown