mcpField
A
api-design

api-design

github.com/xu-xiang開発2.0k39 回インストール34 週間前に更新
リポジトリ

api-design について

REST API の設計・レビュー向けのパターンガイドです。リソース命名、レスポンス、ページネーション、エラー処理、認証、バージョン管理を扱い、TypeScript、Python、Go の例と公開前チェックリストも含みます。

主な機能

  1. 01リソース URL と命名規則
  2. 02HTTP メソッドとステータスコード
  3. 03レスポンス形式とエラー処理
  4. 04オフセット・カーソルページネーション
  5. 05フィルタリング、ソート、検索
  6. 06認証、レート制限、バージョン管理

活用シーン

  1. 01新しい REST API エンドポイントの計画
  2. 02既存 API 契約のレビュー
  3. 03コレクション API のページ方式選定
  4. 04エラー処理とバージョン方針の策定

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/xu-xiang/everything-claude-code-zh --skill api-design

インストール後、Claude Code で/api-designを入力して読み込まれたか確認します。

詳細

API 設計パターン(API Design Patterns)

一貫性があり、開発者にとって使いやすい REST API を設計するための規則とベストプラクティスです。

有効にするタイミング

  • 新しい API エンドポイントを設計するとき
  • 既存の API コントラクトをレビューするとき
  • ページネーション、フィルタリング、並べ替え機能を追加するとき
  • API のエラー処理を実装するとき
  • API のバージョン管理戦略を計画するとき
  • 公開 API またはパートナー向け API を構築するとき

リソース設計(Resource Design)

URL 構造
# 资源是名词、复数、小写、短横线命名 (kebab-case)
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
命名規則
# 正确示例 (GOOD)
/api/v1/team-members          # 多单词资源使用 kebab-case
/api/v1/orders?status=active  # 使用查询参数进行过滤
/api/v1/users/123/orders      # 嵌套资源表示所属关系

# 错误示例 (BAD)
/api/v1/getUsers              # URL 中包含动词
/api/v1/user                  # 使用单数(应使用复数)
/api/v1/team_members          # URL 中使用 snake_case
/api/v1/users/123/getOrders   # 嵌套资源中包含动词

HTTP メソッドとステータスコード

メソッドの意味(Method Semantics)
メソッド(Method)冪等(Idempotent)安全(Safe)用途
GETはいはいリソースの取得
POSTいいえいいえリソースの作成、操作の実行
PUTはいいいえリソースの完全な置き換え
PATCHいいえ*いいえリソースの部分的な更新
DELETEはいいいえリソースの削除

*適切に実装すれば、PATCH も冪等にできます。

ステータスコードのリファレンス
# 成功 (Success)
200 OK                    — GET, PUT, PATCH(包含响应正文)
201 Created               — POST(需包含 Location 标头)
204 No Content            — DELETE, PUT(无响应正文)

# 客户端错误 (Client Errors)
400 Bad Request           — 验证失败、JSON 格式错误
401 Unauthorized          — 缺失或无效的身份验证
403 Forbidden             — 已身份验证但无权访问
404 Not Found             — 资源不存在
409 Conflict              — 重复条目、状态冲突
422 Unprocessable Entity  — 语义无效(JSON 合法,但数据有误)
429 Too Many Requests     — 超出速率限制

# 服务器错误 (Server Errors)
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
# 正确:返回 201 并在 Location 标头中包含路径
HTTP/1.1 201 Created
Location: /api/v1/users/abc-123

レスポンス形式(Response Format)

成功レスポンス
{
  "data": {
    "id": "abc-123",
    "email": "[email protected]",
    "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 状态码进行区分

ページネーション(Pagination)

オフセットベース(Offset-Based)— シンプル
GET /api/v1/users?page=2&per_page=20

# 实现
SELECT * FROM users
ORDER BY created_at DESC
LIMIT 20 OFFSET 20;

長所: 実装が簡単で、「N ページ目に移動」に対応できます。 短所: オフセットが大きい場合(OFFSET 100000)はパフォーマンスが低下し、同時にデータが挿入されると一貫性が損なわれることがあります。

カーソルベース(Cursor-Based)— スケーラブル
GET /api/v1/users?cursor=eyJpZCI6MTIzfQ&limit=20

# 实现
SELECT * FROM users
WHERE id > :cursor_id
ORDER BY id ASC
LIMIT 21;  -- 多获取一个以确定是否有下一页 (has_next)
{
  "data": [...],
  "meta": {
    "has_next": true,
    "next_cursor": "eyJpZCI6MTQzfQ"
  }
}

長所: 位置にかかわらずパフォーマンスが一定で、同時にデータが挿入されても安定します。 短所: 任意のページ番号には移動できず、カーソルは不透明です。

どちらを使うか
使用シナリオページネーションの種類
管理画面、小規模なデータセット(<10K)オフセット(Offset)
無限スクロール、フィード、大規模なデータセットカーソル(Cursor)
公開 APIデフォルトではカーソル(Cursor)、オフセット(Offset)は任意
検索結果オフセット(Offset)(ユーザーは通常、ページ番号を期待します)

フィルタリング、並べ替え、検索(Filtering, Sorting, and Search)

フィルタリング(Filtering)
# 简单等值匹配
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
並べ替え(Sorting)
# 单个字段(前缀 - 表示降序)
GET /api/v1/products?sort=-created_at

# 多个字段(逗号分隔)
GET /api/v1/products?sort=-featured,price,-created_at
全文検索(Full-Text Search)
# 搜索查询参数
GET /api/v1/products?q=wireless+headphones

# 特定字段搜索
GET /api/v1/users?email=alice
スパースフィールドセット(Sparse Fieldsets)
# 仅返回指定的字段(减少数据传输量)
GET /api/v1/users?fields=id,name,email
GET /api/v1/orders?fields=id,total,status&include=customer.name

認証と認可(Authentication and Authorization)

トークンベース認証(Token-Based Auth)
# Authorization 标头中的 Bearer 令牌
GET /api/v1/users
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

# API 密钥(用于服务器对服务器通信)
GET /api/v1/data
X-API-Key: sk_live_abc123
認可パターン(Authorization Patterns)
// 资源级别:检查所有权
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();
});

レート制限(Rate Limiting)

レスポンスヘッダー(Headers)
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."
  }
}
レート制限の段階(Rate Limit Tiers)
レベル(Tier)制限期間使用シナリオ
匿名(Anonymous)30/minIP ごと公開エンドポイント
認証済み(Authenticated)100/minユーザーごと標準 API アクセス
プレミアム(Premium)1000/minAPI キーごと有料 API プラン
内部(Internal)10000/minサービスごとサービス間の呼び出し

バージョン管理(Versioning)

URL パスによるバージョン管理(推奨)
/api/v1/users
/api/v2/users

長所: 明示的で、ルーティングが容易であり、キャッシュ可能です。 短所: バージョン間で URL が変わります。

ヘッダーによるバージョン管理(Header Versioning)
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
   - 在 Sunset 日期后返回 410 Gone
4. 非破坏性更改不需要新版本:
   - 在响应中添加新字段
   - 添加新的可选查询参数
   - 添加新的端点
5. 破坏性更改需要新版本:
   - 删除或重命名字段
   - 更改字段类型
   - 更改 URL 结构
   - 更改身份验证方法

実装パターン(Implementation Patterns)

TypeScript(Next.js API Route)
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}` },
    },
  );
}
Python(Django REST Framework)
from rest_framework import serializers, viewsets, status
from rest_framework.response import Response

class CreateUserSerializer(serializers.Serializer):
    email = serializers.EmailField()
    name = serializers.CharField(max_length=100)

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ["id", "email", "name", "created_at"]

class UserViewSet(viewsets.ModelViewSet):
    serializer_class = UserSerializer
    permission_classes = [IsAuthenticated]

    def get_serializer_class(self):
        if self.action == "create":
            return CreateUserSerializer
        return UserSerializer

    def create(self, request):
        serializer = CreateUserSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = UserService.create(**serializer.validated_data)
        return Response(
            {"data": UserSerializer(user).data},
            status=status.HTTP_201_CREATED,
            headers={"Location": f"/api/v1/users/{user.id}"},
        )
Go(net/http)
func (h *UserHandler) CreateUser(w http.ResponseWriter, r *http.Request) {
    var req CreateUserRequest
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        writeError(w, http.StatusBadRequest, "invalid_json", "Invalid request body")
        return
    }

    if err := req.Validate(); err != nil {
        writeError(w, http.StatusUnprocessableEntity, "validation_error", err.Error())
        return
    }

    user, err := h.service.Create(r.Context(), req)
    if err != nil {
        switch {
        case errors.Is(err, domain.ErrEmailTaken):
            writeError(w, http.StatusConflict, "email_taken", "Email already registered")
        default:
            writeError(w, http.StatusInternalServerError, "internal_error", "Internal error")
        }
        return
    }

    w.Header().Set("Location", fmt.Sprintf("/api/v1/users/%s", user.ID))
    writeJSON(w, http.StatusCreated, map[string]any{"data": user})
}

API 設計チェックリスト(API Design Checklist)

新しいエンドポイントをリリースする前に:

  • リソース URL が命名規則(複数形、kebab-case、動詞なし)に従っている
  • 適切な HTTP メソッドを使用している(読み取りには GET、作成には POST など)
  • 適切なステータスコードを返している(すべてに 200 を返さない)
  • Schema(Zod, Pydantic, Bean Validation)を使って入力を検証している
  • エラーレスポンスが、コードとメッセージを含む標準形式に従っている
  • リストエンドポイントにページネーション(カーソルまたはオフセット)を実装している
  • 認証を必須としている(または公開であることを明示している)
  • 認可を確認している(ユーザーがアクセスできるのは自身のリソースのみ)
  • レート制限を設定している
  • レスポンスから内部の詳細(スタックトレース、SQL エラー)が漏れていない
  • 命名が既存のエンドポイントと一貫している(camelCase と snake_case)
  • ドキュメント化している(OpenAPI/Swagger 仕様を更新している)

コメント