api-design について
言語やフレームワークを問わず、API の設計やレビューに使えるプロトコルレベルの参照スキルです。REST と GraphQL、エラー処理、バージョン管理、ページネーション、認証を扱い、実践的な指針とチェックリストを提供します。
主な機能
- 01REST と GraphQL の選択指針
- 02リソース・HTTP・スキーマの原則
- 03エラー形式とバージョン管理
- 04ページネーションと認証の指針
- 05アンチパターンと品質チェックリスト
活用シーン
- 01API のプロトコルとインターフェースの計画
- 02既存 API の一貫性レビュー
- 03エラーとバージョン戦略の策定
- 04チームの API 設計規約の整備
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ krzysztofsurdy/ code-virtuoso --skill api-designインストール後、Claude Code で/api-designを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/api-evolution.mdgraphql-patterns.mdrest-patterns.md詳細
API 設計
一貫性があり、予測しやすく、進化させやすい API を設計するための原則とパターンです。あらゆる言語やフレームワークに適用できます。実装の詳細ではなく、プロトコルレベルの設計判断に焦点を当てます。
API のサーフェスをプロダクトとして扱います。利用者がソースコードを読まなくても、動作を予測し、エラーから復旧し、統合できるようにします。
使用するタイミング
- 新しいパブリック API または内部 API をゼロから設計するとき
- 既存の API の一貫性と使いやすさをレビューするとき
- プロジェクトで REST と GraphQL のどちらを採用するか選ぶとき
- API のバージョニングや移行戦略を計画するとき
- サービス全体でエラーレスポンスの契約を定義するとき
- チームや組織の API 標準を策定するとき
REST と GraphQL の比較
| 項目 | REST | GraphQL |
|---|---|---|
| 最適な用途 | CRUD が中心で、リソース指向のドメイン | 複雑に関連し、クライアントごとにニーズが異なるデータ |
| データ取得 | エンドポイントごとにレスポンスの形が固定 | クライアントが必要なフィールドを正確に指定 |
| 過剰取得 | よく発生。エンドポイントはリソース全体を返す | 解消。クライアントは必要なデータだけを要求 |
| 取得不足 | よく発生。複数回の往復が必要 | 解消。1 つのクエリで関連データを取得可能 |
| キャッシュ | HTTP キャッシュを標準で利用可能(ETag、Cache-Control) | 独自のキャッシュが必要(正規化ストア、永続化クエリ) |
| ファイルアップロード | マルチパートをネイティブにサポート | 回避策が必要(マルチパート仕様または別エンドポイント) |
| リアルタイム | Webhook、SSE、ポーリング | 仕様にサブスクリプションを組み込み |
| ツールの成熟度 | 成熟:OpenAPI、Postman、HTTP クライアント | 発展中:Apollo、Relay、GraphiQL |
| 学習曲線 | 低め。既存の HTTP の知識を活用 | 高め:スキーマ言語、リゾルバー、クエリの最適化 |
| エラー処理 | HTTP ステータスコードとレスポンスボディ | 実行された操作は通常、レスポンスの errors 配列にエラーを含めて 200 を返します。リクエストエラーには 4xx を使用できます(GraphQL over HTTP は現在もドラフトです) |
| バージョニング | URL パス、ヘッダー、またはクエリパラメーター | 非推奨化と追加変更によるスキーマの進化 |
REST を選ぶ場合: ドメインをリソースと CRUD 操作に対応付けられる場合、HTTP キャッシュが必要な場合、またはクライアントがシンプルな場合(モバイルアプリ、サードパーティ連携など)。
GraphQL を選ぶ場合: クライアントごとに必要なデータが異なる場合、複数のバックエンドサービスを集約する場合、またはフロントエンドとバックエンドの間に厳密な型付けの契約を設けたい場合。
どちらも有効な選択肢です。多くのシステムでは、外部向け/パブリック API に REST を、内部のフロントエンドとバックエンド間の通信に GraphQL を使用しています。
REST 設計の原則
REST API はドメインをリソースとしてモデル化し、HTTP のセマンティクスを使って操作します。
基本ルール:
- リソースには動詞ではなく名詞を使います:
/getOrdersではなく/orders - HTTP メソッドを動詞として使います:GET は読み取り、POST は作成、PUT は置換、PATCH は更新、DELETE は削除
- URL はリソースを識別し、クエリパラメーターはフィルタリング、並べ替え、ページネーションに使います
- コレクションには複数形の名詞を使います:
/users、/users/{id} - ネストは 2 階層までにします:
/users/{id}/ordersは適切ですが、/users/{id}/orders/{id}/items/{id}/variantsは適切ではありません - HTTP ステータスコードを適切な意味で使用します。すべてに 200 を返さないでください
AcceptヘッダーとContent-Typeヘッダーによるコンテンツネゴシエーションをサポートします
HATEOAS(Hypermedia as the Engine of Application State)は、レスポンスにリンクを含めることで発見可能性を高めます。パブリック API では有用ですが、内部サービスでは過剰な場合がよくあります。
{
"id": 42,
"status": "shipped",
"_links": {
"self": { "href": "/orders/42" },
"cancel": { "href": "/orders/42/cancel", "method": "POST" },
"customer": { "href": "/customers/7" }
}
}
詳細な規約については、REST パターンのリファレンスを参照してください。
GraphQL の設計原則
GraphQL API は、クライアントが宣言的にクエリできる強い型付けのスキーマを公開します。
基本ルール:
- スキーマファーストで設計します。リゾルバーを書く前に型システムを定義します
- 型はドメインの概念を表し、フィールドは属性や関連を表します
- クエリはデータを読み取り、ミューテーションはデータを書き込み、サブスクリプションはデータをストリーミングします
- 型システムを使って制約(非 null、列挙型、入力型)を適用します
- 予測できないクエリコストを生む、深くネストされたスキーマは避けます
- バッチ処理(データローダーパターン)で N+1 問題を解決します
- 不正利用を防ぐため、クエリの深さと複雑さを制限します
スキーマファーストの例:
type User {
id: ID!
name: String!
email: String!
orders(first: Int, after: String): OrderConnection!
}
type Order {
id: ID!
total: Float!
status: OrderStatus!
createdAt: DateTime!
}
enum OrderStatus {
PENDING
CONFIRMED
SHIPPED
DELIVERED
CANCELLED
}
詳しい規約については、GraphQL パターンリファレンスを参照してください。
エラー処理
一貫性のあるエラー形式は、API 設計において最も効果の大きい決定の一つです。利用者は、メッセージ文字列を調べなくても、プログラムからエラーを解析できる必要があります。
RFC 9457 Problem Details 形式(REST に推奨。RFC 7807 に取って代わるものです):
Content-Type: application/problem+json とともに送信します。標準の 5 つのメンバー(type、title、status、detail、instance)以外のメンバーは拡張メンバーです。以下の errors などが該当します。
{
"type": "https://api.example.com/errors/insufficient-funds",
"title": "Insufficient Funds",
"status": 422,
"detail": "Account balance is $10.00 but the transfer requires $50.00.",
"instance": "/transfers/abc-123",
"errors": [
{
"field": "amount",
"code": "insufficient_funds",
"message": "Transfer amount exceeds available balance"
}
]
}
主な原則:
- 機械可読な
typeまたはcodeを使用します。クライアントはメッセージではなくコードに基づいて分岐する必要があります - デバッグ用に、人が読める
detailを含めます - 各エラー種別に、安定した文書化済みの
typeURI を割り当てます。HTTP ステータスだけですべてが分かる場合に限り、typeを省略するかabout:blankを使用します - 検証エラーではフィールド単位のエラーを返し、クライアントが該当する入力を強調表示できるようにします
- 適切な HTTP ステータスコード(REST)または構造化されたエラー型(GraphQL)を使用します
- 本番環境では、スタックトレース、内部パス、SQL クエリを決して公開しません
- サービス間でエラーを追跡できるよう、相関 ID/リクエスト ID を含めます
GraphQL のエラー規約:
{
"data": { "createOrder": null },
"errors": [
{
"message": "Insufficient funds",
"extensions": {
"code": "INSUFFICIENT_FUNDS",
"field": "amount"
}
}
]
}
バージョニング
バージョニング戦略によって、既存の利用者との互換性を損なわずに変更をリリースする方法が決まります。
| 戦略 | 仕組み | メリット | デメリット |
|---|---|---|---|
| URL パス | /v1/users | 明示的で、ルーティングが簡単 | URL が煩雑になり、廃止が難しい |
| Accept ヘッダー | Accept: application/vnd.api+json;version=2 | URL がすっきりし、HTTP の仕様に準拠 | 目立ちにくく、気軽なテストが難しい |
| クエリパラメーター | /users?version=2 | 実装が簡単 | 忘れやすく、キャッシュに関する問題がある |
実践的な指針:
- URL パスによるバージョニングが最も一般的で、利用者にも理解しやすい方法です
- 破壊的変更の場合にのみ、メジャーバージョンを上げます
- 新しいバージョンを作るより、API を追加的に進化させる(新しいフィールドやエンドポイントを追加する)ことを優先します
- バージョンを廃止する際は、廃止日を伝え、移行ガイドを提供します
詳しい戦略については、API の進化に関するリファレンスを参照してください。
ページネーション
すべてのリストエンドポイントにはページネーションが必要です。カーソル方式とオフセット方式のどちらを選ぶかによって、パフォーマンス、一貫性、クライアントの複雑さが変わります。
| 方式 | 仕組み | メリット | デメリット |
|---|---|---|---|
| オフセット | ?offset=20&limit=10 | シンプルで、「N ページ目へ移動」に対応 | リアルタイムの挿入/削除があると一貫性がなく、大規模なテーブルでは遅い |
| カーソル | ?after=abc123&limit=10 | リアルタイムデータでも安定し、大規模なデータでも高性能 | 任意のページへ移動できない |
ベストプラクティス:
- 最大ページサイズ(例:100)と適切なデフォルト値(例:20)を設定します
- ページネーションのメタデータとして、
hasNextPage、hasPreviousPage、totalCount(低コストで計算できる場合)を返します totalCountの計算コストが高い場合は、任意項目にするか、推定値を返します- フィード、アクティビティストリーム、頻繁に変更されるデータにはカーソルを使用します
- 管理ダッシュボード、レポート、閲覧中にほとんど変更されないデータセットにはオフセットを使用します
カーソルページネーションのレスポンス例:
{
"data": [ ... ],
"pagination": {
"hasNextPage": true,
"hasPreviousPage": false,
"startCursor": "eyJpZCI6MX0=",
"endCursor": "eyJpZCI6MTB9"
}
}
認証と認可
認証は本人確認です(あなたは誰ですか?)。認可は権限の確認です(何ができますか?)。
| 仕組み | ユースケース | 注意事項 |
|---|---|---|
| API キー | サーバー間連携、シンプルな統合 | 実装は簡単です。定期的にローテーションし、クライアントコードでは決して公開しないでください |
| OAuth 2.0 | サードパーティーアクセス、委任された権限 | 業界標準です。SPA/モバイルでは Authorization Code + PKCE を使用してください |
| JWT(Bearer トークン) | マイクロサービス向けのステートレス認証 | 必要最小限のクレームのみを含め、有効期限を短く設定し、署名とクレームを検証してください |
| セッション Cookie | ブラウザベースのウェブアプリ | CSRF 対策と併用し、Secure、HttpOnly、SameSite フラグを使用してください |
ベストプラクティス:
- 例外なく、常に HTTPS を使用してください
- トークンはクエリ文字列ではなく、
Authorization: Bearer <token>ヘッダーで送信してください - きめ細かなアクセス制御のために、スコープ/権限を実装してください
- 認証情報がない、または無効な場合は
401 Unauthorizedを、権限が不十分な場合は403 Forbiddenを返してください - ブルートフォース攻撃を防ぐため、認証エンドポイントには厳格なレート制限を適用してください
- 再認証を強制せずに済むよう、トークン更新フローをサポートしてください
よくあるアンチパターン
| アンチパターン | 問題 | 対策 |
|---|---|---|
| チャッティー API | ページの表示にクライアントが 10 件以上のリクエストを必要とします | 関連データを集約し、GraphQL または複合エンドポイントを検討してください |
| ゴッドエンドポイント | 単一のエンドポイントが、フラグによって大きく異なるペイロードを受け付けます | 意味が明確な、目的を絞ったエンドポイントに分割してください |
| 命名の不統一 | snake_case、camelCase、複数形/単数形が混在します | 規約を 1 つ選び、プロジェクト全体で徹底してください |
| ページネーションの欠如 | リストエンドポイントが上限のない結果を返します | コレクションは必ずページネーションし、最大ページサイズを設定してください |
| バージョン管理なしの破壊的変更 | フィールドの名前変更や削除により、クライアントが気付かないまま動作しなくなります | バージョン管理を行うか、追加のみの進化にしてください |
| 内部情報の漏えい | データベースのカラム名や URL 内の自動採番 ID が露出します | 安定した外部識別子(UUID、スラッグ)にマッピングしてください |
| 冪等性の無視 | POST を再試行するとリソースが重複して作成されます | 非冪等操作では冪等性キーをサポートしてください |
| すべてに 200 を返す | エラーがエラーボディとともに HTTP 200 で返されます | 適切な HTTP ステータスコードを使用してください |
| タイムゾーンなしのタイムスタンプ | 2024-01-15 14:30:00 は曖昧です | 必ずタイムゾーン付きの ISO 8601 を使用してください:2024-01-15T14:30:00Z |
品質チェックリスト
API をリリースまたはレビューする前に、次の点を確認してください:
- リソースの命名に一貫性がある(複数形の名詞を使い、URL に動詞を含めない)
- HTTP メソッドが意味に合っている(GET は安全であり、PUT/DELETE は冪等である)
- すべてのリストエンドポイントが最大ページサイズ付きでページネーションされている
- エラーレスポンスの形式が統一され、機械可読なコードが使われている
- 認証が必須であり、HTTPS を使用している
- 適切なヘッダー(
Retry-AfterとRateLimitフィールド、または従来のX-RateLimit-*)を伴うレート制限が設定されている - 破壊的変更がバージョン管理されているか、追加のみの進化によって回避されている
- すべてのエンドポイントにリクエスト/レスポンスの例がある
- タイムスタンプにタイムゾーン付きの ISO 8601 を使用している
- ID が内部の自動採番値ではなく、安定した外部識別子である
- ブラウザクライアント向けに CORS が設定されている(該当する場合)
- レスポンスで圧縮(gzip/brotli)が有効になっている
- API ドキュメントが信頼できる唯一の情報源(OpenAPI スキーマ、GraphQL イントロスペクション)から生成されている