mcpskills.net
技能MCP智能体提示词
mcpskills.net — A curated directory of AI agent Skills and MCP servers
TermsPrivacy
← 返回技能
Development

API 和接口设计

指导稳定的 API 和接口设计。在设计 API、模块边界或任何公共接口时使用。

作者:Addy Osmani仓库 →来源 →

设计稳定、文档完善的接口,使其难以被误用。

何时使用

  • 设计新的 API 端点
  • 定义团队之间的模块边界或契约
  • 创建组件 prop 接口
  • 建立影响 API 形状的数据库模式
  • 修改现有的公共接口

核心原则

Hyrum 定律

当一个 API 有足够多的用户时,你系统的所有可观测行为都会被某人依赖。

每一种公共行为——包括未记录的怪癖——一旦被用户依赖,就成为了事实上的契约。

单版本规则

为一个同时只存在一个版本的世界设计——扩展而非分叉。

1. 契约优先

先定义接口再实现:

interface TaskAPI {
  createTask(input: CreateTaskInput): Promise<Task>;
  listTasks(params: ListTasksParams): Promise<PaginatedResult<Task>>;
  getTask(id: string): Promise<Task>;
  updateTask(id: string, input: UpdateTaskInput): Promise<Task>;
  deleteTask(id: string): Promise<void>;
}

2. 一致的错误语义

interface APIError {
  error: {
    code: string;        // 机器可读:"VALIDATION_ERROR"
    message: string;     // 人类可读:"Email is required"
    details?: unknown;
  };
}

// 状态码映射
// 400 → 客户端发送了无效数据
// 401 → 未认证
// 403 → 已认证但未授权
// 404 → 资源未找到
// 422 → 验证失败
// 500 → 服务器错误(绝不暴露内部细节)

3. 在边界处验证

app.post('/api/tasks', async (req, res) => {
  const result = CreateTaskSchema.safeParse(req.body);
  if (!result.success) {
    return res.status(422).json({
      error: {
        code: 'VALIDATION_ERROR',
        message: 'Invalid task data',
        details: result.error.flatten(),
      },
    });
  }

  const task = await taskService.create(result.data);
  return res.status(201).json(task);
});

4. 优先添加而非修改

// 好:添加可选字段
interface CreateTaskInput {
  title: string;
  description?: string;
  priority?: 'low' | 'medium' | 'high';  // 后来添加的,可选
}

// 差:更改现有字段类型
interface CreateTaskInput {
  title: string;
  priority: number;  // 从 string 改为 number —— 破坏消费者
}

5. 可预测的命名

| 模式 | 约定 | 示例 | |------|------|------| | REST 端点 | 复数名词,无动词 | GET /api/tasks | | 查询参数 | camelCase | ?sortBy=createdAt | | 响应字段 | camelCase | { createdAt, updatedAt } | | 布尔字段 | is/has/can 前缀 | isComplete | | 枚举值 | UPPER_SNAKE | "IN_PROGRESS" |

REST API 模式

资源设计

GET    /api/tasks              → 列出任务
POST   /api/tasks              → 创建任务
GET    /api/tasks/:id          → 获取单个任务
PATCH  /api/tasks/:id          → 更新任务(部分更新)
DELETE /api/tasks/:id          → 删除任务

分页

// 请求
GET /api/tasks?page=1&pageSize=20&sortBy=createdAt&sortOrder=desc

// 响应
{
  "data": [...],
  "pagination": {
    "page": 1,
    "pageSize": 20,
    "totalItems": 142,
    "totalPages": 8
  }
}

部分更新(PATCH)

// 只更改标题,其他保持不变
PATCH /api/tasks/123
{ "title": "Updated title" }

TypeScript 接口模式

可辨识联合(Discriminated Unions)

type TaskStatus =
  | { type: 'pending' }
  | { type: 'in_progress'; assignee: string; startedAt: Date }
  | { type: 'completed'; completedAt: Date; completedBy: string }
  | { type: 'cancelled'; reason: string; cancelledAt: Date };

function getStatusLabel(status: TaskStatus): string {
  switch (status.type) {
    case 'pending': return 'Pending';
    case 'in_progress': return `In progress (${status.assignee})`;
    case 'completed': return `Done on ${status.completedAt}`;
    case 'cancelled': return `Cancelled: ${status.reason}`;
  }
}

品牌类型(Branded Types)

type TaskId = string & { readonly __brand: 'TaskId' };
type UserId = string & { readonly __brand: 'UserId' };

function getTask(id: TaskId): Promise<Task> { ... }

验证

设计 API 后:

  • [ ] 每个端点都有类型化的输入和输出模式
  • [ ] 错误响应遵循单一一致的格式
  • [ ] 验证仅在系统边界处进行
  • [ ] 列表端点支持分页
  • [ ] 新字段是增量且可选的(向后兼容)
  • [ ] 命名在所有端点间遵循一致的约定