APIレスポンスフォーマット:一貫性のあるAPIのベストプラクティス
一貫性のないAPIレスポンスは、フロントエンド開発者から最も多い不満の一つです。すべてのエンドポイントが異なる形式でデータを返すと、クライアントコードは特殊なケースだらけになります。一貫したレスポンスフォーマットは開発者体験を向上させ、バグを減らし、APIを自己文書化するものにします。
レスポンスエンベロープ
すべてのレスポンスを一貫した構造でラップします:
成功(単一リソース)
{
"data": {
"id": "user_123",
"name": "Alice",
"email": "alice@example.com",
"createdAt": "2024-01-15T10:30:00Z"
},
"meta": {
"requestId": "req_abc123"
}
}
成功(コレクション)
{
"data": [
{ "id": "user_123", "name": "Alice" },
{ "id": "user_456", "name": "Bob" }
],
"meta": {
"total": 142,
"page": 1,
"perPage": 20,
"requestId": "req_def456"
}
}
エラー
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{ "field": "email", "message": "Invalid email format" }
]
},
"meta": {
"requestId": "req_ghi789"
}
}
重要な原則:クライアントは常にトップレベルで data または error を確認します。成功データとエラー情報を同じレスポンスに混在させてはいけません。
レスポンスフォーマットの検証にはJSONバリデーターをお使いください。
HTTPステータスコード
ステータスコードを正しく使いましょう — クライアントが最初に確認するものです:
成功コード
| コード | 使用場面 |
|---|---|
| 200 OK | GETの成功、PUT/PATCHの成功 |
| 201 Created | リソースを作成したPOST |
| 204 No Content | DELETEの成功(ボディなし) |
クライアントエラーコード
| コード | 使用場面 |
|---|---|
| 400 Bad Request | 不正なリクエスト構文 |
| 401 Unauthorized | 認証の欠落または無効 |
| 403 Forbidden | 認証済みだが権限なし |
| 404 Not Found | リソースが存在しない |
| 409 Conflict | リソース状態の競合(重複) |
| 422 Unprocessable | 構文は正しいがセマンティックエラー |
| 429 Too Many Requests | レート制限超過 |
サーバーエラーコード
| コード | 使用場面 |
|---|---|
| 500 Internal Server Error | 予期しないサーバー障害 |
| 502 Bad Gateway | 上流サービスの障害 |
| 503 Service Unavailable | 一時的な過負荷またはメンテナンス |
エラーレスポンスの設計
良いエラーレスポンスは開発者が素早くデバッグするのに役立ちます:
{
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "User with ID 'user_999' not found",
"details": [],
"documentationUrl": "https://api.example.com/docs/errors#RESOURCE_NOT_FOUND"
},
"meta": {
"requestId": "req_xyz789",
"timestamp": "2024-01-15T10:30:00Z"
}
}
ルール:
codeは機械可読(定数文字列、HTTPステータスではない)messageは人間可読(クライアントを壊さずに変更可能)detailsはバリデーション失敗時のフィールドレベルエラーを提供requestIdはサポートチームがログでリクエストを追跡可能にする
ページネーションパターン
オフセットベース
シンプルで任意のページへのジャンプをサポート:
{
"data": ["..."],
"meta": {
"page": 2,
"perPage": 20,
"total": 142,
"totalPages": 8
},
"links": {
"first": "/api/users?page=1&per_page=20",
"prev": "/api/users?page=1&per_page=20",
"next": "/api/users?page=3&per_page=20",
"last": "/api/users?page=8&per_page=20"
}
}
カーソルベース
リアルタイムデータや大規模データセットに適しています:
{
"data": ["..."],
"meta": {
"hasNext": true,
"hasPrev": true
},
"links": {
"next": "/api/users?cursor=eyJpZCI6MTQzfQ&limit=20",
"prev": "/api/users?cursor=eyJpZCI6MTIzfQ&limit=20&direction=prev"
}
}
日時
常にISO 8601形式でタイムゾーン情報を含めてください:
{
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T14:22:33+00:00",
"expiresAt": "2024-12-31T23:59:59Z"
}
以下は使用しないでください:
- Unixタイムスタンプ(曖昧:秒かミリ秒か?)
- タイムゾーンなしのローカル日付
- MM/DD/YYYYなどのカスタムフォーマット
タイムスタンプの処理について詳しくはUnixタイムスタンプガイドをご覧ください。
Null vs 存在しないフィールド
2つの一般的なアプローチ:
nullを含める(明示的):
{ "name": "Alice", "avatar": null, "bio": null }
存在しないフィールドを省略(疎):
{ "name": "Alice" }
推奨事項:API内で一貫性を保ちましょう。明示的なnullは型付き言語に適しています(クライアントがフィールドの存在を知ることができます)。疎な方法は帯域幅が制限された環境に適しています。
レスポンスのバージョニング
レスポンスフォーマットが変更される場合、APIをバージョニングします:
GET /api/v2/users/123
バージョニングが必要な破壊的変更:
- フィールドの削除
- フィールドの型変更
- フィールドの名前変更
- レスポンスエンベロープ構造の変更
バージョニング不要の非破壊的変更:
- 新しいフィールドの追加
- 新しいエンドポイントの追加
- 新しいenum値の追加
FAQ
成功レスポンスをdataキーでラップすべきか、リソースを直接返すべきか?
data ラッパーを使用すると、すべてのレスポンスに一貫した構造を提供し、データと並んでメタデータ、ページネーション、リンクのための余地を残せます。リソースを直接返す方が単一リソースのエンドポイントではシンプルです。ほとんどの最新のAPIは一貫性のためにラッパーアプローチを使用しています。
バッチ操作で部分的な失敗をどう処理すべきか?
成功と失敗の両方をdataオブジェクトに含む200レスポンスを返します。207 Multi-Status(WebDAV)を使用することもできますが、REST APIではあまり一般的ではありません。
関連リソース
- JSONフォーマッター — APIレスポンスのフォーマット
- JSON APIデザインパターン — 包括的なAPI設計ガイド
- JSONスキーマバリデーション — レスポンス構造の検証