跳转至

API 设计模式

用于设计一致且开发者友好的 REST API 的约定与最佳实践。

启用时机

  • 在设计新的 API 端点时
  • 在审查现有 API 契约时
  • 在添加分页、过滤或排序时
  • 在实现 API 错误处理时
  • 在规划 API 版本管理策略时
  • 在构建面向公共或合作伙伴的 API 时

资源设计

URL 结构

# リソースは名詞、複数形、小文字、ケバブケース
GET    /api/v1/users
GET    /api/v1/users/:id
POST   /api/v1/users
PUT    /api/v1/users/:id
PATCH  /api/v1/users/:id
DELETE /api/v1/users/:id

# 関係のための サブ リソース
GET    /api/v1/users/:id/orders
POST   /api/v1/users/:id/orders

# CRUD にマップされないアクション (動詞は慎重に使用)
POST   /api/v1/orders/:id/cancel
POST   /api/v1/auth/login
POST   /api/v1/auth/refresh

命名规则

# よい
/api/v1/team-members          # 複数単語リソース用ケバブケース
/api/v1/orders?status=active  # フィルタリング用クエリ パラメーター
/api/v1/users/123/orders      # 所有権用のネストされたリソース

# 悪い
/api/v1/getUsers              # URL 内の動詞
/api/v1/user                  # 単数形(複数形を使用)
/api/v1/team_members          # URL 内のスネークケース
/api/v1/users/123/getOrders   # ネストされたリソース内の動詞

HTTP 方法与状态码

方法语义

方法 幂等 安全 用途
GET 是 是 获取资源
POST 否 否 创建资源、触发操作
PUT 是 否 完全替换资源
PATCH 否* 否 部分更新资源
DELETE 是 否 删除资源

*PATCH 在正确实现下可以是幂等的。

状态码参考

# 成功
200 OK                    — GET、PUT、PATCH(応答本体付き)
201 Created               — POST (Location ヘッダーを含める)
204 No Content            — DELETE、PUT(応答本体なし)

# クライアント エラー
400 Bad Request           — 検証失敗、不正な JSON
401 Unauthorized          — 認証がない、または無効
403 Forbidden             — 認証済みですが認可されていない
404 Not Found             — リソースが存在しません
409 Conflict              — 重複エントリ、状態競合
422 Unprocessable Entity  — セマンティック上無効(有効な JSON、悪いデータ)
429 Too Many Requests     — レート制限を超過

# サーバー エラー
500 Internal Server Error — 予期しない失敗 (詳細は公開しない)
502 Bad Gateway           — アップストリーム サービスが失敗
503 Service Unavailable   — 一時的なオーバーロード、Retry-After を含める

常见错误

# 悪い: すべてに 200
{ "status": 200, "success": false, "error": "Not found" }

# よい: HTTP ステータス コードをセマンティック的に使用
HTTP/1.1 404 Not Found
{ "error": { "code": "not_found", "message": "User not found" } }

# 悪い: 検証エラーに 500
# よい: フィールドレベルの詳細を含む 400 または 422

# 悪い: 作成されたリソースに 200
# よい: Location ヘッダー付き 201
HTTP/1.1 201 Created
Location: /api/v1/users/abc-123

响应格式

成功响应

{
  "data": {
    "id": "abc-123",
    "email": "alice@example.com",
    "name": "Alice",
    "created_at": "2025-01-15T10:30:00Z"
  }
}

集合响应(包含分页)

{
  "data": [
    { "id": "abc-123", "name": "Alice" },
    { "id": "def-456", "name": "Bob" }
  ],
  "meta": {
    "total": 142,
    "page": 1,
    "per_page": 20,
    "total_pages": 8
  },
  "links": {
    "self": "/api/v1/users?page=1&per_page=20",
    "next": "/api/v1/users?page=2&per_page=20",
    "last": "/api/v1/users?page=8&per_page=20"
  }
}

错误响应

{
  "error": {
    "code": "validation_error",
    "message": "Request validation failed",
    "details": [
      {
        "field": "email",
        "message": "Must be a valid email address",
        "code": "invalid_format"
      },
      {
        "field": "age",
        "message": "Must be between 0 and 150",
        "code": "out_of_range"
      }
    ]
  }
}

响应信封变体

// オプション A: データ ラッパー付きエンベロープ(パブリック API に推奨)
interface ApiResponse<T> {
  data: T;
  meta?: PaginationMeta;
  links?: PaginationLinks;
}

interface ApiError {
  error: {
    code: string;
    message: string;
    details?: FieldError[];
  };
}

// オプション B: フラット応答(シンプル、内部 API 向け)
// 成功: リソースを直接返す
// エラー: エラー オブジェクトを返す
// HTTP ステータス コードで区別

分页

基于偏移(简单)

GET /api/v1/users?page=2&per_page=20

# 実装
SELECT * FROM users
ORDER BY created_at DESC
LIMIT 20 OFFSET 20;

优点: 实现简单,支持“跳转到第 N 页” 缺点: 偏移量较大(OFFSET 100000)时速度较慢,并发插入时会出现不一致

基于游标(可扩展)

GET /api/v1/users?cursor=eyJpZCI6MTIzfQ&limit=20

# 実装
SELECT * FROM users
WHERE id > :cursor_id
ORDER BY id ASC
LIMIT 21;  -- 次が있는지 判定するため 1 つ余分に取得
{
  "data": [...],
  "meta": {
    "has_next": true,
    "next_cursor": "eyJpZCI6MTQzfQ"
  }
}

优点: 无论位置如何,性能都保持一致,在并发插入时表现稳定 缺点: 无法跳转到任意页,游标不透明

如何选择

使用场景 分页类型
管理仪表盘、小型数据集(<10K) 偏移
无限滚动、信息流、大型数据集 游标
公开 API 游标(默认)与偏移(可选)
搜索结果 偏移(用户期望页码)

过滤

# シンプルな等価性
GET /api/v1/orders?status=active&customer_id=abc-123

# 比較演算子(括弧表記を使用)
GET /api/v1/products?price[gte]=10&price[lte]=100
GET /api/v1/orders?created_at[after]=2025-01-01

# 複数値(カンマ区切り)
GET /api/v1/products?category=electronics,clothing

# ネストされたフィールド(ドット表記)
GET /api/v1/orders?customer.country=US

排序

# 単一フィールド (降順用に - を頭に付ける)
GET /api/v1/products?sort=-created_at

# 複数フィールド(カンマ区切り)
GET /api/v1/products?sort=-featured,price,-created_at
# 検索クエリ パラメーター
GET /api/v1/products?q=wireless+headphones

# フィールド固有の検索
GET /api/v1/users?email=alice

稀疏字段集

# 指定されたフィールドのみを返す(ペイロード削減)
GET /api/v1/users?fields=id,name,email
GET /api/v1/orders?fields=id,total,status&include=customer.name

认证与授权

基于令牌的认证

# Authorization ヘッダー内のベアラー トークン
GET /api/v1/users
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

# API キー(サーバー間)
GET /api/v1/data
X-API-Key: sk_live_abc123

授权模式

// リソース レベル: 所有権を確認
app.get("/api/v1/orders/:id", async (req, res) => {
  const order = await Order.findById(req.params.id);
  if (!order) return res.status(404).json({ error: { code: "not_found" } });
  if (order.userId !== req.user.id) return res.status(403).json({ error: { code: "forbidden" } });
  return res.json({ data: order });
});

// ロール ベース: 権限を確認
app.delete("/api/v1/users/:id", requireRole("admin"), async (req, res) => {
  await User.delete(req.params.id);
  return res.status(204).send();
});

速率限制

HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1640000000

# 超過した場合
HTTP/1.1 429 Too Many Requests
Retry-After: 60
{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded. Try again in 60 seconds."
  }
}

速率限制层级

层级 限制 窗口 使用场景
匿名 30/分钟 每个 IP 公共端点
已认证 100/分钟 每个用户 标准 API 访问
高级 1000/分钟 每个 API 密钥 付费 API 计划
内部 10000/分钟 每个服务 服务间通信

版本管理

/api/v1/users
/api/v2/users

优点: 明确、路由简单、可缓存
缺点: 不同版本间 URL 会变化

请求头版本管理

GET /api/users
Accept: application/vnd.myapp.v2+json

优点: URL 简洁
缺点: 难以测试、容易遗漏

版本管理策略

1. /api/v1/ から開始 — 必要になるまでバージョン管理しないでください
2. 最大 2 つのアクティブ バージョンを保守(現在 + 前)
3. 廃止予定のタイムライン:
   - 廃止予定を発表(パブリック API には 6 か月前の通知)
   - Sunset ヘッダーを追加: Sunset: Sat, 01 Jan 2026 00:00:00 GMT
   - 廃止予定日後に 410 Gone を返す
4. 非破壊的な変更はバージョン新規が必要ありません:
   - 応答への新しいフィールドの追加
   - 新しいオプション クエリ パラメーターの追加
   - 新しいエンドポイントの追加
5. 破壊的な変更には新しいバージョンが必要です:
   - フィールドの削除または名前変更
   - フィールド型の変更
   - URL 構造の変更
   - 認証方法の変更

实现模式

TypeScript (Next.js API 路由)

import { z } from "zod";
import { NextRequest, NextResponse } from "next/server";

const createUserSchema = z.object({
  email: z.string().email(),
  name: z.string().min(1).max(100),
});

export async function POST(req: NextRequest) {
  const body = await req.json();
  const parsed = createUserSchema.safeParse(body);

  if (!parsed.success) {
    return NextResponse.json({
      error: {
        code: "validation_error",
        message: "Request validation failed",
        details: parsed.error.issues.map(i => ({
          field: i.path.join("."),
          message: i.message,
          code: i.code,
        })),
      },
    }, { status: 422 });
  }

  const user = await createUser(parsed.data);

  return NextResponse.json(
    { data: user },
    {
      status: 201,
      headers: { Location: `/api/v1/users/${user.id}` },
    },
  );
}

API 设计检查清单

在将新端点部署到生产环境之前:

  • [ ] 资源 URL 遵循命名规范(复数形式、短横线小写、无动词)
  • [ ] 使用正确的 HTTP 方法(读取用 GET、创建用 POST 等)
  • [ ] 返回合适的状态码(并非全部使用 200)
  • [ ] 输入通过模式进行验证(Zod、Pydantic、Bean Validation)
  • [ ] 错误响应遵循标准格式(包含代码和消息)
  • [ ] 列表端点实现分页(游标或偏移量)
  • [ ] 需要认证(或明确标记为公共)
  • [ ] 确认授权(用户只能访问自己的资源)
  • [ ] 设置速率限制
  • [ ] 响应不泄露内部细节(堆栈跟踪、SQL 错误)
  • [ ] 与现有端点的命名保持一致(camelCase 与 snake_case)
  • [ ] 已文档化(更新 OpenAPI/Swagger 规范)

本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 ev/ECC