JSON API Best Practices for Developers
Design better JSON APIs with consistent naming, structured responses, proper pagination, error handling, and performance optimizations used by top engineering teams.
なぜAPI設計が重要なのか
適切に設計されたJSON APIは、統合にかかる時間を数時間から数週間に変えることがあります。良いAPI設計はサポートチケットを減らし、ドキュメントを短くし、消費者がドキュメントを読む前に物事の動作を予測できるようにします。
このガイドのパターンは理論的なものではなく、Stripe、GitHub、Slackなど、優れた開発者体験で知られる企業のAPIから得られたものです。それぞれを見ていきましょう。
命名規則
JSONプロパティ名の2つの主要なスタイルはcamelCaseとsnake_caseです。どちらでも構いませんが、一つを選んでどこでも使用してください。
| 規則 | 例 | 使用例 | エコシステム |
|---|---|---|---|
camelCase | firstName, createdAt | Google APIs, Azure | JavaScript / TypeScript フロントエンド |
snake_case | first_name, created_at | Stripe, GitHub, Slack | Python / Ruby バックエンド |
ガイドライン: 主な消費者エコシステムに合った規則を選択してください。APIが主にJavaScriptフロントエンドによって消費される場合、camelCaseは摩擦を避けます。バックエンドがPythonまたはRubyで、APIがB2Bの場合、snake_caseが慣用的です。
どちらを選んでも、絶対に一貫性を保ってください。同じレスポンス内でuser_nameとfirstNameを混在させることは、ほぼ他の何よりも信頼を損なう原因となります。
一貫したレスポンス構造
APIからのすべてのレスポンスは、予測可能なエンベロープに従うべきです。ほとんどのAPIに適したパターンは以下の通りです。
成功レスポンス
{
"data": {
"id": "usr_abc123",
"email": "alice@example.com",
"name": "アリス・ジョンソン",
"created_at": "2025-01-15T09:30:00Z"
},
"meta": {
"request_id": "req_7f2a9c"
}
}
リストレスポンス
{
"data": [
{ "id": "usr_abc123", "name": "アリス・ジョンソン" },
{ "id": "usr_def456", "name": "ボブ・スミス" }
],
"meta": {
"total": 142,
"page": 1,
"per_page": 20,
"request_id": "req_8b3d1e"
}
}
エラーレスポンス
{
"error": {
"code": "validation_error",
"message": "リクエストボディに無効なフィールドが含まれています。",
"details": [
{
"field": "email",
"message": "有効なメールアドレスである必要があります。"
},
{
"field": "age",
"message": "13歳以上である必要があります。"
}
]
},
"meta": {
"request_id": "req_4c8e2a"
}
}
重要な原則: 成功時にはdataが存在し、失敗時にはerrorが存在し、metaはリクエストトレーシングとページネーションのために両方に現れます。消費者は最初にerrorをチェックする単一のレスポンスハンドラーを書くことができます。
自分で試してみてください: APIレスポンスを私たちのJSONフォーマッタに貼り付けて、構造を検査し、一貫性の欠如を見つけてください。
ページネーションパターン
ほとんどのリストエンドポイントにはページネーションが必要です。異なるトレードオフを持つ3つの一般的なアプローチがあります。
1. オフセットページネーション
最もシンプルなアプローチ。クライアントがページ番号またはオフセットを指定します:
GET /api/users?page=3&per_page=20
{
"data": [...],
"meta": {
"total": 142,
"page": 3,
"per_page": 20,
"total_pages": 8
}
}
2. カーソルページネーション
ページ番号の代わりに、サーバーは次の結果セットを指す不透明なカーソルを返します:
GET /api/users?limit=20&after=cursor_abc123
{
"data": [...],
"meta": {
"has_more": true,
"next_cursor": "cursor_def456"
}
}
3. キーセットページネーション
実際のカラム値(タイムスタンプやIDなど)をカーソルとして使用します。これは不透明さのないカーソルページネーションです:
GET /api/users?limit=20&created_after=2025-01-15T09:30:00Z
{
"data": [...],
"meta": {
"has_more": true,
"next_created_after": "2025-01-16T14:22:00Z"
}
}
ページネーション比較
| 特徴 | オフセット | カーソル | キーセット |
|---|---|---|---|
| ページNにジャンプ | はい | いいえ | いいえ |
| 総カウント | 簡単に含められる | 高コスト | 高コスト |
| 大規模テーブルでのパフォーマンス | 低下する(OFFSETが行をスキャン) | 一貫している | 一貫している |
| 挿入/削除の処理 | アイテムをスキップまたは重複する可能性がある | 安定している | 安定している |
| 実装の複雑さ | 低い | 中程度 | 中程度 |
| 使用例 | 従来のウェブアプリ | Stripe, Slack | Twitter / X |
ガイドライン: 管理ダッシュボードや内部ツールでは、ユーザーが特定のページにジャンプする必要があるため、オフセットページネーションを使用してください。公開API、無限スクロールUI、および頻繁に変更されるデータセットにはカーソルまたはキーセットページネーションを使用してください。
エラーハンドリング
良いエラーレスポンスは、消費者に何が間違っていたのか、どこで、理想的にはどのように修正するかを伝えます。推奨する構造は以下の通りです:
{
"error": {
"code": "rate_limit_exceeded",
"message": "1分あたりのリクエスト制限100を超えました。",
"details": [],
"doc_url": "https://docs.example.com/errors/rate-limit"
}
}
codeは機械可読の文字列です(HTTPステータスコードではなく、実際のHTTPレスポンスに含まれます)。messageは人間が読める形式です。details配列は、検証失敗のためのフィールドレベルのエラーを提供します。そしてdoc_urlはエラーに関するドキュメントへのリンクです。
一般的なエラーコードとそのHTTPステータスマッピング:
| HTTPステータス | エラーコード | 使用するタイミング |
|---|---|---|
| 400 | validation_error | リクエストボディがスキーマ検証に失敗した場合 |
| 401 | unauthorized | 認証が欠落または無効な場合 |
| 403 | forbidden | 認証済みだが権限が不足している場合 |
| 404 | not_found | リソースが存在しない場合 |
| 409 | conflict | 重複リソースまたは状態の競合がある場合 |
| 422 | unprocessable_entity | 有効なJSONだが意味的に誤っている場合 |
| 429 | rate_limit_exceeded | リクエストが多すぎる場合 |
| 500 | internal_error | 予期しないサーバーの失敗 |
データ型とフォーマット
データ型を正しく設定することで、統合バグの全カテゴリを防ぎます。
日付と時刻
常にISO 8601形式を使用し、タイムゾーン情報を含めます:
{
"created_at": "2025-01-15T09:30:00Z",
"expires_at": "2025-02-15T23:59:59+05:30"
}
レスポンスボディ内でUnixタイムスタンプを使用しないでください — 人間が読めず、タイムゾーンの曖昧さを生み出します。Unixタイムスタンプが必要な場合(例: JWTクレーム用)、明確に文書化してください。
大きな数値
JavaScriptのNumber型は2^53 - 1(9,007,199,254,740,991)を超える精度を失います。IDや金銭的価値がこれを超える場合は、文字列を使用してください:
{
"id": "9223372036854775807",
"amount": "99.99",
"currency": "USD"
}
Stripe、Twitter、Discordはこの理由で文字列IDを使用しています。
Nullと欠落
明確な規則を定義し、文書化してください:
null— フィールドは存在するが値がない(例: ユーザーがまだバイオを設定していない)。- 欠落キー — フィールドはこのリソースに関連しないか、リクエストされなかった(例: 部分レスポンス内)。
多くのAPIは一貫した形状を維持するためにnullフィールドを含めており、これによりクライアントがオプショナルチェイニングなしで型付きインターフェースを書くのが容易になります。
ブール値
実際のJSONブール値を使用し、文字列や整数は使用しないでください:
// 良い
{ "is_active": true }
// 悪い
{ "is_active": "true" }
{ "is_active": 1 }
バージョニング戦略
APIは進化します。既存の消費者を壊さずに変更を行うための戦略が必要です。主な3つのアプローチ:
- URLバージョニング:
/v1/users,/v2/users。最も一般的で目に見えるアプローチ。Stripe、GitHub、Twilioで使用されています。理解しやすく、ルーティングも簡単ですが、コードの重複を招く可能性があります。 - ヘッダーバージョニング:
Accept: application/vnd.api+json;version=2。クリーンなURLですが、発見しにくい。GitHub(代替として)やAzureで使用されています。 - クエリパラメータ:
/users?version=2。シンプルですが、クエリ文字列を汚染します。実際にはあまり一般的ではありません。
ガイドライン: URLバージョニングは実用的なデフォルトです。明示的でキャッシュ可能で、すべてのHTTPクライアントで機能します。特定の技術的理由がない限り、逸脱しないでください。
圧縮
JSONはテキストであり、非常に良く圧縮されます。APIレスポンスの圧縮を常に有効にしてください:
| アルゴリズム | 圧縮率 | 速度 | ブラウザサポート |
|---|---|---|---|
| gzip | 良好(70–80%削減) | 速い | ユニバーサル |
| Brotli (br) | より良い(75–85%削減) | 圧縮はやや遅い | すべての現代のブラウザ |
典型的な50KBのJSONレスポンスに対して、gzipは約10–15KBに、Brotliは約8–12KBに圧縮します。サーバーはAccept-Encodingヘッダーを尊重し、それに応じて選択する必要があります。ほとんどのリバースプロキシ(Nginx、Cloudflare)はこれを自動的に処理します。
パフォーマンステクニック
部分レスポンス(フィールド選択)
消費者が必要なフィールドのみをリクエストできるようにします。これによりペイロードサイズとデータベースの負荷が軽減されます:
GET /api/users?fields=id,name,email
{
"data": [
{ "id": "usr_abc123", "name": "アリス", "email": "alice@example.com" }
]
}
Google APIsはこれを「部分レスポンス」と呼び、すべてのエンドポイントでサポートしています。特に遅い接続のモバイルクライアントにとって価値があります。
スパースフィールドセット
関連リソースを返すAPIでは、消費者が各リソースタイプに含まれるフィールドを制御できるようにします:
GET /api/articles?include=author&fields[article]=title,body&fields[author]=name
これはJSON:API仕様からのパターンです。クライアント側でのN+1クエリ問題を排除し、ペイロードを最小限に保ちます。
ETagと条件付きリクエスト
レスポンスにETagヘッダーを返します。クライアントは後続のリクエストでIf-None-Matchを送信し、何も変更されていなければ304 Not Modified(空のボディ)を返します。これにより、頻繁に変更されないリソースの帯域幅と処理時間を節約できます。
まとめチェックリスト
APIを出荷する前に、このチェックリストを確認してください:
- すべてのエンドポイントで一貫した命名規則(
camelCaseまたはsnake_case)。 data、error、metaを含む予測可能なレスポンスエンベロープ。- 文書化された制限を持つすべてのリストエンドポイントでのページネーション。
- 機械可読のコード、メッセージ、フィールドレベルの詳細を持つ構造化されたエラーレスポンス。
- ISO 8601の日付、大きな数値のための文字列ID、実際のブール値。
- 文書化され、施行されたAPIバージョニング戦略。
- gzipまたはBrotli圧縮が有効。
- パフォーマンスに敏感な消費者のためのフィールド選択またはスパースフィールドセット。
自分で試してみてください: 私たちのJSONフォーマッタを使用して、APIレスポンスをフォーマットおよび検証し、クリーンで一貫した構造に従っていることを確認してください。