api-design-patterns について
api-design-patterns は、実装前の API 契約の設計とレビューに使うスキルです。エンドポイントの命名、スキーマ、ページネーション、エラー、バージョニング、ドキュメントを案内し、設計内容を api-design.md にまとめます。
主な機能
- 01URL と HTTP メソッドの規約
- 02リクエストとレスポンスのスキーマ統一
- 03ページネーション、フィルタリング、ソート
- 04エラー形式とバージョニングの定義
- 05OpenAPI ドキュメント作成の指針
活用シーン
- 01新しい API 契約の設計
- 02エンドポイントの一貫性レビュー
- 03ページネーションとフィルタリングの計画
- 04API エラーとバージョン方針の定義
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ hieutrtr/ ai1-skills --skill api-design-patternsインストール後、Claude Code で/api-design-patternsを入力して読み込まれたか確認します。
同梱ファイルreferences/endpoint-catalog-template.mdreferences/pydantic-schema-examples.md
詳細
API 設計パターン
使用する場面
次の場合にこのスキルを使用します。
- 新しい API エンドポイントを設計する場合、または既存のエンドポイントの契約を変更する場合
- 機能のリクエスト/レスポンススキーマを定義する場合
- エンドポイント間でページネーション、フィルタリング、ソートを標準化する場合
- 一貫性のあるエラーレスポンス形式を設計する場合
- API のバージョン管理や非推奨化の方針を計画する場合
- 実装前に API 契約の一貫性をレビューする場合
- フロントエンド/バックエンド間の連携のため、エンドポイントの仕様を文書化する場合
入力: plan.md または architecture.md が存在する場合は、機能の範囲とアーキテクチャ上の決定事項を把握するために読み込みます。それ以外の場合は、ユーザーのリクエストに基づいて作業します。
出力: API 設計を api-design.md に書き込みます。ユーザーには次のように伝えます。「API 設計を api-design.md に書き込みました。/task-decomposition を実行して実装タスクを作成するか、/python-backend-expert を実行して実装してください。」
次の場合、このスキルを使用しないでください。
- 実装コードを書く場合(
python-backend-expertを使用) - システムレベルのアーキテクチャを決定する場合(
system-architectureを使用) - エンドポイントのテストを書く場合(
pytest-patternsを使用) - フロントエンドのデータ取得を実装する場合(
react-frontend-expertを使用)
手順
URL の命名規則
リソースの命名ルール
- コレクションには複数形の名詞を使用します:
/users、/orders、/products - 複数語のリソースにはケバブケースを使用します:
/order-items、/user-profiles - ID で指定するリソースには単数形を使用します:
/users/{user_id}、/orders/{order_id} - ネストは最大 2 階層にします:
/users/{user_id}/orders(/users/{user_id}/orders/{order_id}/items/{item_id}のようにはしません) - URL に動詞を含めません。代わりに HTTP メソッドを使用します(
/ordersへのPOSTとし、/orders/createにはしません) - フィルタリング、ソート、ページネーションにはクエリパラメーターを使用します:
/users?role=admin&sort=-created_at
URL 構造のテンプレート
/{version}/{resource} → Collection (list, create)
/{version}/{resource}/{id} → Single resource (get, update, delete)
/{version}/{resource}/{id}/{sub-resource} → Nested collection
/{version}/{resource}/actions/{action} → Non-CRUD operations (rarely needed)
命名例
| 良い例 | 悪い例 | 理由 |
|---|---|---|
GET /v1/users | GET /v1/getUsers | 動詞は不要です。HTTP メソッドがアクションを示します |
POST /v1/users | POST /v1/user/create | コレクションへの POST は作成を意味します |
GET /v1/order-items | GET /v1/orderItems | キャメルケースではなく、ケバブケースを使用します |
GET /v1/users/{id}/orders | GET /v1/users/{id}/orders/{oid}/items | ネストは最大 2 階層です |
POST /v1/orders/{id}/actions/cancel | POST /v1/cancelOrder/{id} | CRUD 以外の操作にはアクション用サブリソースを使用します |
HTTP メソッドの意味
| メソッド | 目的 | リクエストボディ | 成功ステータス | 冪等性 |
|---|---|---|---|---|
GET | リソースを取得する | なし | 200 OK | あり |
POST | 新しいリソースを作成する | 必須 | 201 Created | なし |
PUT | 全体を置き換える | 必須(全体) | 200 OK | あり |
PATCH | 部分的に更新する | 必須(部分) | 200 OK | なし* |
DELETE | リソースを削除する | なし | 204 No Content | あり |
*PATCH は本質的に冪等ではありませんが、適切に実装すれば冪等にできます。
作成時のレスポンスヘッダー:
201を返すPOSTには、作成されたリソースの URL を示すLocationヘッダーを含めることが望ましいです。
条件付きリクエスト:
- 頻繁にアクセスされるリソースを持つ GET エンドポイントで、キャッシュのために
If-None-Match/ETagをサポートします
スキーマの命名規則(Pydantic v2)
すべての Pydantic スキーマで、一貫した命名パターンに従ってください。
| パターン | 用途 | フィールド |
|---|---|---|
{Resource}Create | POST リクエストボディ | 書き込み可能なフィールド。id とタイムスタンプは含めません |
{Resource}Update | PUT リクエストボディ | すべての書き込み可能なフィールドが必須です |
{Resource}Patch | PATCH リクエストボディ | すべてのフィールドが Optional です |
{Resource}Response | 単一リソースのレスポンス | id、タイムスタンプを含むすべてのフィールド |
{Resource}ListResponse | ページネーションされたリストのレスポンス | items + ページネーションのメタデータ |
{Resource}Filter | クエリパラメーター | Optional なフィルターフィールド |
スキーマ設計ルール:
- Response スキーマでは、内部フィールド(hashed_password、internal_notes)を決して公開しないでください
- Response スキーマには必ず
idとタイムスタンプ(created_at、updated_at)を含めてください - ORM モデルをレスポンススキーマに変換するには
model_validate(orm_instance)を使用してください - PATCH 操作では
model_dump(exclude_unset=True)を使用して、「未指定」と「null に設定」を区別してください - 具体例は
references/pydantic-schema-examples.mdを参照してください
ページネーション
カーソルベースのページネーション(デフォルト)
すべてのリストエンドポイントでカーソルベースのページネーションを使用してください。大規模なデータセットではオフセットベースよりパフォーマンスが高く、「ウィンドウのずれ」の問題も回避できます。
リクエストパラメーター:
GET /v1/users?cursor=eyJpZCI6MTAwfQ&limit=20
| パラメーター | 型 | デフォルト | 説明 |
|---|---|---|---|
cursor | str | None | None | 前回のレスポンスから取得した不透明なカーソル |
limit | int | 20 | 1 ページあたりの項目数(最大 100) |
レスポンス形式:
{
"items": [...],
"next_cursor": "eyJpZCI6MTIwfQ",
"has_more": true
}
カーソルの実装:
- 最後の項目のソートキー(通常は
id)を Base64 文字列としてエンコードします - カーソルはクライアントにとって不透明なものであり、解析や生成をしてはいけません
WHERE id > :last_id ORDER BY id ASC LIMIT :limit + 1を使用します。has_moreを判定するため、1 件多く取得します
オフセットベースのページネーション(必要な場合)
クライアントが任意のページに移動する必要がある場合(管理者用テーブルなど)に限り、オフセットベースを使用してください。
{
"items": [...],
"total": 150,
"page": 2,
"page_size": 20,
"total_pages": 8
}
フィルタリングと並べ替え
フィルタリング
フィールド名を使ったクエリパラメーターを使用してください。
GET /v1/users?role=admin&is_active=true&created_after=2024-01-01
フィルタリングの規則:
- 完全一致:
?field=value - 範囲:
?field_min=10&field_max=100または?created_after=...&created_before=... - 検索:
?q=search+term(複数のフィールドを対象とする全文検索) - 複数の値:
?status=active&status=pending(OR の意味)
並べ替え
フィールド名と方向のプレフィックスを指定する sort クエリパラメーターを使用してください。
GET /v1/users?sort=-created_at → descending by created_at
GET /v1/users?sort=name → ascending by name
GET /v1/users?sort=-created_at,name → multi-field sort
規則: - プレフィックスは降順、プレフィックスなしは昇順を意味します。
エラーレスポンス形式
すべての API エラーは、一貫した形式に従います。
{
"detail": "Human-readable error message",
"code": "MACHINE_READABLE_CODE",
"field_errors": [
{
"field": "email",
"message": "Invalid email format",
"code": "INVALID_FORMAT"
}
]
}
標準エラーコードとステータスの対応
| HTTP ステータス | 使用する場合 | 例 code |
|---|---|---|
400 | 不正なリクエスト | BAD_REQUEST |
401 | 認証情報がない、または無効 | UNAUTHORIZED |
403 | 認証済みだが、認可されていない | FORBIDDEN |
404 | リソースが見つからない | NOT_FOUND |
409 | 競合(重複、バージョンの不一致) | CONFLICT |
422 | 検証エラー(Pydantic) | VALIDATION_ERROR |
429 | レート制限を超過 | RATE_LIMITED |
500 | 予期しないサーバーエラー | INTERNAL_ERROR |
エラースキーマ(Pydantic v2):
class FieldError(BaseModel):
field: str
message: str
code: str
class ErrorResponse(BaseModel):
detail: str
code: str
field_errors: list[FieldError] = []
API のバージョン管理
戦略:URL プレフィックスによるバージョン管理
/v1/users → Version 1
/v2/users → Version 2
バージョン管理のルール:
- 新しい API はすべて
/v1/から開始します - 破壊的変更の場合のみメジャーバージョンを上げます
- 破壊的でない変更(新しい任意フィールド、新しいエンドポイント)では、新しいバージョンは不要です
- 同時にサポートするアクティブなバージョンは最大 2 つです
新しいバージョンが必要となる破壊的変更:
- レスポンスからフィールドを削除する
- フィールドの型を変更する
- リクエストの任意フィールドを必須にする
- 既存のエンドポイントの URL 構造を変更する
- エラーレスポンスの形式を変更する
非推奨化の手順:
- 古いバージョンに
Deprecationヘッダーを追加します:Deprecation: true - 廃止日を指定した
Sunsetヘッダーを追加します:Sunset: Sat, 01 Mar 2026 00:00:00 GMT - 新しいバージョンを指す
Linkヘッダーを追加します:Link: </v2/users>; rel="successor-version" - 監視のため、非推奨のエンドポイントの使用状況を記録します
- サンセット日以降に古いバージョンを削除します
OpenAPI ドキュメント
FastAPI は OpenAPI スキーマを自動的に生成します。以下の内容を追加します:
@router.get(
"/users/{user_id}",
response_model=UserResponse,
summary="Get user by ID",
description="Retrieve a single user's details by their unique identifier.",
responses={
404: {"model": ErrorResponse, "description": "User not found"},
},
tags=["Users"],
)
async def get_user(user_id: int) -> UserResponse:
...
ドキュメント作成の規約:
- すべてのエンドポイントに
summary(短い説明)と任意のdescription(詳細な説明)を設定します - 200 以外のすべてのレスポンスを、そのスキーマとともにドキュメント化します
- リソース名に合わせた
tagsでエンドポイントをグループ化します response_modelを使用してレスポンススキーマを自動的にドキュメント化します
例
Products API コントラクトの設計
目的: 検索とページネーションに対応する /v1/products CRUD エンドポイントのコントラクトを設計します。
エンドポイント:
| Method | Path | Description | Request | Response | Status |
|---|---|---|---|---|---|
| GET | /v1/products | 商品一覧 | クエリ:cursor、limit、q、category、sort | ProductListResponse | 200 |
| POST | /v1/products | 商品を作成 | ボディ:ProductCreate | ProductResponse | 201 |
| GET | /v1/products/{id} | 商品を取得 | — | ProductResponse | 200 |
| PATCH | /v1/products/{id} | 商品を更新 | ボディ:ProductPatch | ProductResponse | 200 |
| DELETE | /v1/products/{id} | 商品を削除 | — | — | 204 |
スキーマ:
class ProductCreate(BaseModel):
name: str = Field(min_length=1, max_length=200)
description: str | None = None
price_cents: int = Field(gt=0)
category: str
sku: str = Field(pattern=r"^[A-Z0-9-]+$")
class ProductPatch(BaseModel):
name: str | None = None
description: str | None = None
price_cents: int | None = Field(default=None, gt=0)
category: str | None = None
class ProductResponse(BaseModel):
id: int
name: str
description: str | None
price_cents: int
category: str
sku: str
created_at: datetime
updated_at: datetime
class ProductListResponse(BaseModel):
items: list[ProductResponse]
next_cursor: str | None
has_more: bool
検索とフィルタリング:
GET /v1/products?q=laptop&category=electronics&sort=-price_cents&limit=20
完全なドキュメントテンプレートについては、references/endpoint-catalog-template.md を参照してください。
その他のスキーマ例については、references/pydantic-schema-examples.md を参照してください。
エッジケース
一括操作
複数のリソースを一度に操作する場合:
POST /v1/users/bulk
リクエスト:
{
"items": [
{"email": "[email protected]", "name": "Alice"},
{"email": "[email protected]", "name": "Bob"}
]
}
レスポンス(一部成功 — ステータス 207):
{
"results": [
{"index": 0, "status": "created", "data": {...}},
{"index": 1, "status": "error", "error": {"detail": "Email already exists", "code": "CONFLICT"}}
],
"succeeded": 1,
"failed": 1
}
個々の項目がそれぞれ独立して成功または失敗する場合は、HTTP 207 Multi-Status を使用します。
ファイルアップロードのエンドポイント
ファイルのアップロードには JSON ではなく、multipart/form-data を使用します:
@router.post("/v1/files", response_model=FileResponse, status_code=201)
async def upload_file(
file: UploadFile,
description: str = Form(default=""),
) -> FileResponse:
...
処理する前に、ファイルサイズと MIME タイプを検証します。サイズが大きすぎるファイルには 413 Payload Too Large を返します。
長時間実行される操作
通常のリクエストタイムアウト内に完了できない操作の場合:
-
ステータス URL とともに
202 Acceptedを返します:{"status_url": "/v1/jobs/abc123", "estimated_completion": "2024-01-15T10:30:00Z"} -
クライアントがステータス URL をポーリングします:
GET /v1/jobs/abc123 → {"status": "processing", "progress": 0.65} GET /v1/jobs/abc123 → {"status": "completed", "result_url": "/v1/reports/xyz"}
サブリソースの設計
リソースが論理的に親リソースに属していても、ネストが 2 階層を超える場合は、フィルターを使ったトップレベルのリソースを使用します:
# Instead of: GET /v1/users/{id}/orders/{oid}/items
# Use: GET /v1/order-items?order_id=123
これにより、フィルタリングを通じて関係を維持しながら、URL をフラットに保てます。
出力ファイル
プロジェクトルートの api-design.md に API 設計を書き込みます:
# API Design: [Feature Name]
## Endpoints
| Method | URL | Description | Auth |
|--------|-----|-------------|------|
| GET | /v1/users | List users | Required |
| POST | /v1/users | Create user | Required |
## Request/Response Schemas
### UserCreate
| Field | Type | Required | Validation |
|-------|------|----------|------------|
| email | string | Yes | Valid email |
| name | string | Yes | 1-100 chars |
### UserResponse
| Field | Type | Description |
|-------|------|-------------|
| id | uuid | User ID |
| email | string | User email |
## Error Codes
| Code | HTTP Status | Description |
|------|-------------|-------------|
| USER_NOT_FOUND | 404 | User does not exist |
| EMAIL_EXISTS | 409 | Email already registered |
## Next Steps
- Run `/task-decomposition` to create implementation tasks
- Run `/python-backend-expert` to implement endpoints