REST API設計のベストプラクティス:開発者に愛されるAPIを構築する
· 12分で読める
📑 目次
よく設計されたAPIは使うのが楽しいものです。設計が不十分なAPIは、フラストレーション、バグ、そしてチームのリソースを消耗するサポートチケットを生み出します。
APIがマイクロサービス、モバイルアプリ、サードパーティ統合、AIエージェントを接続する現代のソフトウェアアーキテクチャの基盤となるにつれて、設計を正しく行うことがこれまで以上に重要になっています。成功するAPIと開発者が放棄するAPIの違いは、単に機能性だけではありません。予測可能性、一貫性、そして開発者体験が重要なのです。
この包括的なガイドでは、優れたAPIと平凡なAPIを分けるプラクティスを、実世界の例と今日から実装できる実用的なアドバイスとともにカバーします。
REST APIの基礎
REST(Representational State Transfer)は厳格なプロトコルではなく、アーキテクチャスタイルです。その核となる原則を理解することで、API開発プロセス全体を通じてより良い設計決定を下すことができます。
RESTアーキテクチャの6つの指針となる制約は以下の通りです:
- クライアント・サーバー分離: クライアントとサーバーは独立して動作し、それぞれが個別に進化できる
- ステートレス通信: 各リクエストには必要な情報がすべて含まれ、サーバーはリクエスト間でクライアントのコンテキストを保存しない
- キャッシュ可能なレスポンス: レスポンスはキャッシュ可能かどうかを明示的に示し、パフォーマンスを向上させる
- 統一インターフェース: リソースはリクエストで識別され、クライアントは表現を通じてリソースを操作する
- 階層化システム: クライアントは、エンドサーバーに直接接続しているか中間サーバーに接続しているかを判別できない
- コードオンデマンド(オプション): サーバーは実行可能なコードを転送することでクライアントの機能を拡張できる
実際には、REST APIはHTTPメソッドを意味的に使用します:GETはデータを取得し、POSTはリソースを作成し、PUTはリソース全体を置き換え、PATCHは部分的に更新し、DELETEはリソースを削除します。URLは動詞としてのアクションではなく、名詞としてのリソースを表します。
プロのヒント: ステートレス性は、維持するのが最も難しい原則であることが多いです。サーバーにセッションデータを保存しないでください。代わりに、必要な認証と認可情報をすべて含むトークン(JWTなど)を使用してください。
URL設計:リソースと命名
URL構造は、開発者がAPIを探索する際に最初に遭遇するものです。直感的で予測可能なURLは認知負荷を軽減し、APIを学習しやすく記憶しやすくします。
リソース指向設計
APIをアクション(動詞)ではなくリソース(名詞)を公開するものとして考えてください。HTTPメソッドがアクションを示すため、URLは操作対象を識別するだけで済みます。
| 良い例 ✅ | 悪い例 ❌ | 理由 |
|---|---|---|
GET /users |
GET /getUsers |
HTTPメソッドがすでに「取得」を意味している |
GET /users/123 |
GET /user?id=123 |
リソース識別子はパスに属する |
POST /users |
POST /createUser |
HTTPメソッドが「作成」を意味している |
DELETE /users/123 |
POST /deleteUser/123 |
適切なHTTPメソッドを使用する |
GET /users/123/orders |
GET /getUserOrders?userId=123 |
階層的な関係がより明確 |
命名規則
命名の一貫性は混乱を防ぎ、エラーを減らします。以下のルールに従ってください:
- コレクションには複数形の名詞を使用:
/users、/products、/orders - ハイフン付きの小文字を使用:
/user-profiles、/userProfilesや/user_profilesではない - 関連リソースを論理的にネスト:
/users/123/orders/456 - ネストの深さを制限: 2〜3レベルを超える場合は、クエリパラメータまたは別のエンドポイントを使用
- ファイル拡張子を避ける:
/users、/users.jsonではない(代わりにAcceptヘッダーを使用) - データベースIDではなくリソースIDを使用: 公開識別子にはUUIDまたはスラッグを検討
非リソース操作の処理
リソースモデルにきれいに適合しない操作を公開する必要がある場合があります。このような場合は、操作自体をリソースとして扱います:
POST /users/123/password-reset
POST /orders/456/cancellation
POST /reports/generate
GET /search?q=laptop&category=electronics
これらのエンドポイントはアクションまたはプロセスを表しており、代替案が不自然なリソースマッピングを強制する場合は許容されます。
クイックヒント: URLを設計する際は、新しい開発者に説明することを想像してください。URLが特定の方法で構造化されている理由を説明するのに1文以上必要な場合、おそらく複雑すぎます。
HTTPメソッドとステータスコード
HTTPは操作を記述するための豊富な語彙を提供します。メソッドとステータスコードを正しく使用することで、APIが予測可能になり、キャッシュ、デバッグ、標準HTTPツールとの統合が容易になります。
HTTPメソッド
| メソッド | アクション | 成功コード | 冪等性 | 安全性 |
|---|---|---|---|---|
GET |
リソースを取得 | 200 OK | はい | はい |
POST |
新しいリソースを作成 | 201 Created | いいえ | いいえ |
PUT |
リソース全体を置換 | 200 OK / 204 No Content | はい | いいえ |
PATCH |
部分更新 | 200 OK | いいえ* | いいえ |
DELETE |
リソースを削除 | 204 No Content | はい | いいえ |
HEAD |
ヘッダーのみ取得 | 200 OK | はい | はい |
OPTIONS |
許可されたメソッドを取得 | 200 OK | はい | はい |
*PATCHは冪等に設計できますが、仕様では保証されていません
冪等性の理解
冪等な操作は、何回実行しても同じ結果を生成します。この特性は、ネットワーク障害が再試行を引き起こす可能性がある分散システムの信頼性にとって重要です。
GET /users/123は冪等です — 1回または100回呼び出しても同じユーザーデータが返されます。DELETE /users/123も冪等です — 最初の呼び出しでユーザーが削除され、その後の呼び出しは404になりますが、最終状態は同じです。
POST /usersは冪等ではありません — 各呼び出しで新しいユーザーが作成されます。冪等な作成が必要な場合は、クライアント生成IDでPUTを使用するか、冪等キーを実装してください。
必須のステータスコード
200と500だけを使用しないでください。適切なステータスコードは、クライアントがレスポンスボディを解析せずに正しく応答を処理するのに役立ちます。
成功コード(2xx):
200 OK— ボディ付きの標準成功レスポンス201 Created— リソースが正常に作成された(Locationヘッダーを含める)204 No Content— レスポンスボディなしの成功(DELETEで一般的)202 Accepted— 非同期処理のためにリクエストが受け入れられた
クライアントエラーコード(4xx):
400 Bad Request— 無効なリクエスト構文または検証失敗401 Unauthorized— 認証が必要または失敗403 Forbidden— 認証済みだが認可されていない404 Not Found— リソースが存在しない409 Conflict— リクエストが現在の状態と競合(例:重複メール)422 Unprocessable Entity— 整形式リクエストの検証エラー429 Too Many Requests— レート制限を超過
サーバーエラーコード(5xx):
500 Internal Server Error— 一般的なサーバーエラー502 Bad Gateway— アップストリームサーバーからの無効なレスポンス503 Service Unavailable— 一時的な過負荷またはメンテナンス504 Gateway Timeout— アップストリームサーバーのタイムアウト
プロのヒント: 429と503のレスポンスには常にRetry-Afterヘッダーを含めて、クライアントにいつ再試行できるかを伝えてください。これにより、サービスが回復したときのサンダリングハード問題を防ぎます。
エラーレスポンス設計
エラーレスポンスは、多くのAPIが不十分な部分です。不可解なエラーメッセージは、5分で修正できる問題を何時間ものデバッグに変えてしまいます。エラーレスポンスは一貫性があり、有益で、実行可能である必要があります。
標準エラー形式
すべてのエラーレスポンスで一貫した構造を使用してください:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "リクエストに無効なデータが含まれています",
"details": [
{
"field": "email",
"message": "メールアドレスは既に登録されています",
"code": "DUPLICATE_EMAIL"
},
{
"field": "password",
"message": "パスワードは8文字以上である必要があります",
"code": "PASSWORD_TOO_SHORT"
}
],
"request_id": "req_7f8a9b2c3d4e5f6g",
"documentation_url": "https://api.example.com/docs/errors/validation"
}
}
エラーレスポンスの構成要素
- 機械可読コード: クライアントがプログラム的に処理できる一貫したエラーコードを使用
- 人間可読メッセージ: エンドユーザーに表示するのに適した明確な説明
- フィールドレベルの詳細: 検証エラーの場合、どのフィールドが失敗したか、その理由を指定
- リクエストID: サポートとデバッグのための一意の識別子を含める
- ドキュメントリンク: 解決手順の関連ドキュメントを指す
検証エラーのベストプラクティス
最初に遭遇したエラーだけでなく、すべての検証エラーを一度に返してください。開発者は、1つのエラーを修正しただけで別のエラーを発見するというモグラ叩きをする必要はありません。
POST /users
{
"email": "invalid-email",
"password": "123",
"age": -5
}
レスポンス: 422 Unprocessable Entity
{
"error": {
"code": "VALIDATION_ERROR",
"message": "リクエストの検証に失敗しました",
"details": [
{
"field": "email",
"message": "有効なメールアドレスである必要があります",
"code": "INVALID_FORMAT"
},
{
"field": "password",
"message": "8文字以上である必要があります",
"code": "TOO_SHORT"
},
{
"field": "age",
"message": "正の数である必要があります",
"code": "INVALID_VALUE"
}
]
}
}
セキュリティ上の考慮事項
エラーメッセージで機密情報を漏らさないように注意してください。ユーザーアカウントが存在するかどうかを明らかにしたり、内部システムの詳細を公開したり、本番環境でスタックトレースを提供したりしないでください。
次の代わりに:「ユーザー[email protected]が見つかりません」
次を使用:「無効なメールまたはパスワード」
詳細なエラー情報はサーバー側でログに記録しますが、クライアントにはサニタイズされたメッセージを返してください。
ページネーションとフィルタリング
単一のレスポンスで数千のレコードを返すと、パフォーマンスが低下し、ユーザー体験が悪化します。ページネーションは、コレクションを返すすべてのエンドポイントに不可欠です。
ページネーション戦略
オフセットベースのページネーションはシンプルで馴染みがあります:
GET /users?limit=20&offset=40
レスポンス:
{
"data": [...],
"pagination": {
"limit": 20,
"offset": 40,
"total": 1247,
"has_more": true
}
}
長所:実装が簡単、任意のページへのジャンプをサポート
短所:大きなオフセットでパフォーマンスが低下、リクエスト間でデータが変更されると結果が不整合
カーソルベースのページネーションは大規模データセットに対してより堅牢です:
GET /users?limit=20&cursor=eyJpZCI6MTIzNDU2fQ
レスポンス:
{
"data": [...],
"pagination": {
"next_cursor": "eyJpZCI6MTIzNDc2fQ",
"has_more": true
}
}
長所:一貫した結果、より良いパフォーマンス、リアルタイムデータを処理
短所:任意のページにジャンプできない、実装がやや複雑
ページベースのページネーションはUIにとってユーザーフレンドリーです:
GET /users?page=3&per_page=20
レスポンス:
{
"data": [...],
"pagination": {
"page": 3,
"per_page": 20,
"total_pages": 63,
"total_items": 1247
}
}
フィルタリングとソート
クライアントがクエリパラメータを使用して結果をフィルタリングおよびソートできるようにします:
GET /users?status=active&role=admin&sort=-created_at,name
一般的なパターン:
- 等価:
?status=active - 比較:
?age_gt=18&age_lt=65