REST APIを設計・実装するときに押さえておきたい原則と慣習をまとめたガイドです。


目次

  1. リソース設計とURI
  2. HTTPメソッド
  3. HTTPステータスコード
  4. リクエスト / レスポンス形式
  5. エラーハンドリング
  6. ページネーション・フィルタ・ソート
  7. バージョニング
  8. 認証・認可とセキュリティ
  9. 冪等性と並行制御
  10. キャッシュ
  11. レート制限
  12. 非同期処理
  13. ドキュメントと運用
  14. チェックリスト

1. リソース設計とURI

基本ルール

ルール良い例悪い例
名詞を使う(動詞はHTTPメソッドで表す)GET /usersGET /getUsers
コレクションは複数形/users, /orders/user, /order
小文字 + ケバブケース/order-items/OrderItems, /order_items
末尾スラッシュなし/users/123/users/123/
ファイル拡張子を含めない/users/123(Acceptで指定)/users/123.json

階層(サブリソース)

親子関係が明確な場合のみネストし、深さは 2階層程度まで に抑えます。

GET /users/123/orders          # ユーザー123の注文一覧
GET /users/123/orders/456      # 深すぎる場合は ↓ を検討
GET /orders/456                # 注文IDが一意なら直接アクセス

CRUDに収まらない操作

どうしても動詞的な操作が必要な場合は、以下のいずれかで表現します。

# 1. サブリソースとして表現(推奨)
POST /orders/456/cancellation

# 2. アクションをサブパスにする
POST /orders/456/cancel

# 3. Google AIP流のカスタムメソッド
POST /orders/456:cancel

チーム内でどれか1つに統一することが重要です。

IDの扱い

  • 連番IDは推測・列挙されやすいため、外部公開するならUUIDやULIDなどの推測困難なIDを検討する
  • IDは文字列として扱う(クライアント側の数値精度問題を避けるため)

2. HTTPメソッド

メソッド用途安全冪等ボディ
GET取得✅✅なし
POST作成・処理の実行❌❌あり
PUT全体置換(存在しなければ作成も可)❌✅あり
PATCH部分更新❌❌※あり
DELETE削除❌✅通常なし
HEADヘッダのみ取得✅✅なし
OPTIONS対応メソッドの確認(CORSプリフライト)✅✅なし

※ PATCHは実装次第で冪等にできます。

注意点

  • GETで状態を変更しない(クローラーやプリフェッチで意図せず実行される)
  • PUT と PATCH を使い分ける。PUTで送られなかったフィールドは「削除/初期化」と解釈されるのが本来の意味
  • PATCHの形式は JSON Merge Patch (RFC 7396) または JSON Patch (RFC 6902) を採用すると仕様が明確になる

3. HTTPステータスコード

よく使うコード

2xx 成功

コード用途
200 OK一般的な成功(GET, PUT, PATCH)
201 Createdリソース作成成功。Location ヘッダで新リソースのURIを返す
202 Accepted非同期処理を受け付けた
204 No Content成功・返すボディなし(DELETEなど)

3xx リダイレクト

コード用途
301 / 308恒久的な移動(308はメソッドを維持)
304 Not Modified条件付きGETでキャッシュが有効

4xx クライアントエラー

コード用途
400 Bad Requestリクエストの形式不正(JSONが壊れている等)
401 Unauthorized未認証(認証情報がない・無効)
403 Forbidden認証済みだが権限がない
404 Not Foundリソースが存在しない
405 Method Not Allowedメソッド非対応(Allow ヘッダを返す)
409 Conflict状態の競合(重複作成、楽観ロック失敗など)
412 Precondition FailedIf-Match 等の前提条件不一致
415 Unsupported Media Type非対応の Content-Type
422 Unprocessable Content形式は正しいがバリデーションエラー
429 Too Many Requestsレート制限超過(Retry-After を返す)

5xx サーバーエラー

コード用途
500 Internal Server Error予期しないサーバーエラー
502 Bad Gateway上流サーバーからの不正応答
503 Service Unavailable一時的に利用不可(Retry-After を返す)
504 Gateway Timeout上流サーバーのタイムアウト

アンチパターン

  • エラーでも 200 OK を返し、ボディに "success": false を入れる
  • すべてのエラーを 400 や 500 にまとめる
  • 権限のないリソースの存在を隠したい場合は 403 ではなく 404 を返すことも検討する

4. リクエスト / レスポンス形式

基本

  • Content-Type: application/json を標準にする(文字コードはUTF-8)
  • フィールド名の命名規則を 統一 する(camelCase か snake_case のどちらか)
  • 日時は ISO 8601 / RFC 3339 形式で、タイムゾーンを含める
{
  "id": "01HF8X2K3M4N5P6Q7R8S9T0V1W",
  "name": "山田 太郎",
  "email": "taro@example.com",
  "createdAt": "2026-04-01T09:30:00Z",
  "updatedAt": "2026-04-02T12:00:00+09:00"
}

設計のポイント

  • 金額は小数(float)を避ける:最小通貨単位の整数か、文字列の10進数で表す
    { "amount": 1500, "currency": "JPY" }
  • null と欠落の扱いを決める:「値がない」を null で返すのか、キーごと省略するのかを統一する
  • 列挙値は文字列 にする("status": "shipped")。数値コードは可読性が低い
  • 真偽値は肯定形 で命名する(isActive ○ / isNotDeleted ×)
  • トップレベルは配列ではなくオブジェクト にする(メタ情報を後から追加できる)
    {
      "data": [ ... ],
      "pagination": { ... }
    }
  • レスポンスに内部実装を漏らさない(DBのカラム名、スタックトレース、内部IDなど)

部分取得・関連リソースの展開

必要に応じて、フィールドの絞り込みや関連の展開を提供します。

GET /users/123?fields=id,name,email
GET /orders/456?expand=customer,items

5. エラーハンドリング

統一フォーマットを使う

RFC 9457 (Problem Details for HTTP APIs) に準拠すると、標準化されたエラー形式になります。

HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json
{
  "type": "https://api.example.com/problems/validation-error",
  "title": "入力内容に誤りがあります",
  "status": 422,
  "detail": "2件のフィールドでバリデーションエラーが発生しました",
  "instance": "/users",
  "traceId": "a1b2c3d4e5f6",
  "errors": [
    { "field": "email", "code": "invalid_format", "message": "メールアドレスの形式が正しくありません" },
    { "field": "age",   "code": "out_of_range",   "message": "0以上150以下で指定してください" }
  ]
}

ポイント

  • 機械可読なエラーコード(code / type)と 人間向けメッセージ を分ける。クライアントはメッセージ文字列で分岐しない
  • バリデーションエラーは 全フィールドをまとめて 返す(1件ずつだと往復が増える)
  • トレースID / リクエストID を含め、ログと突き合わせられるようにする
  • 5xxでは内部情報(SQL、スタックトレース、ファイルパス)を返さない

6. ページネーション・フィルタ・ソート

ページネーション

方式例長所短所
オフセット?offset=40&limit=20実装が簡単、任意ページへ移動可大きいオフセットで遅い、データ追加時にずれる
ページ番号?page=3&perPage=20UIと相性が良いオフセットと同じ欠点
カーソル(推奨)?cursor=eyJpZCI6MTAwfQ&limit=20高速・一貫性が高い任意ページへジャンプできない

カーソル方式のレスポンス例:

{
  "data": [ ... ],
  "pagination": {
    "nextCursor": "eyJpZCI6MTIwfQ",
    "hasMore": true
  }
}
  • limit には デフォルト値と上限 を設ける(例:デフォルト20、最大100)
  • 総件数(totalCount)は大規模データでは高コストなので、必要な場合のみ返す

フィルタ

GET /orders?status=shipped&customerId=123
GET /orders?createdAt[gte]=2026-01-01&createdAt[lt]=2026-02-01
GET /products?tags=sale,new

ソート

GET /products?sort=-price,name    # price降順 → name昇順

検索

全文検索など複雑な条件は専用エンドポイントにするのも一案です。

GET  /products/search?q=キーボード
POST /products/search             # 条件が複雑でURLに収まらない場合

7. バージョニング

方式

方式例備考
URIパス(最も一般的)/v1/usersわかりやすく、ルーティング・キャッシュが容易
ヘッダApi-Version: 2026-04-01URIがきれい。日付ベース(Stripe方式)も有名
メディアタイプAccept: application/vnd.example.v2+jsonRESTの考え方に忠実だが扱いが煩雑

破壊的変更と非破壊的変更

非破壊的(バージョンを上げなくてよい)

  • 新しいエンドポイントの追加
  • レスポンスへの任意フィールド追加
  • 任意のリクエストパラメータ追加

破壊的(新バージョンが必要)

  • フィールドの削除・名前変更・型変更
  • 必須パラメータの追加
  • ステータスコードやエラー形式の変更
  • デフォルト挙動の変更

運用

  • クライアントは 未知のフィールドを無視する よう実装してもらう(前方互換性)
  • 廃止予定のAPIは Deprecation / Sunset ヘッダで事前に通知する
    Deprecation: @1767225600
    Sunset: Thu, 31 Dec 2026 23:59:59 GMT
    Link: <https://api.example.com/docs/migration-v2>; rel="deprecation"

8. 認証・認可とセキュリティ

認証

  • 常にHTTPS(TLS) を使う。HTTPは受け付けないかリダイレクトではなく拒否する
  • ユーザー委任のアクセスには OAuth 2.0 / OpenID Connect を使う
  • トークンは Authorization: Bearer <token> ヘッダで送る。URLのクエリに含めない(ログやRefererに残る)
  • アクセストークンは短命にし、リフレッシュトークンで更新する
  • サーバー間通信のAPIキーはローテーション可能にしておく

認可

  • リソース単位で権限チェック を行う(IDを書き換えて他人のデータにアクセスできるBOLA/IDOR脆弱性を防ぐ)
  • 一括代入(Mass Assignment)を防ぐ:role や isAdmin のようなフィールドをリクエストから直接更新させない
  • 最小権限の原則に従い、スコープを細かく設計する

入力と出力

  • すべての入力をサーバー側でバリデーションする(型、長さ、範囲、形式)
  • リクエストボディサイズに上限を設ける
  • SQLインジェクション等を防ぐため、パラメータ化クエリを使う
  • レスポンスには必要なデータだけを返す(過剰なデータ露出を避ける)

ヘッダ

Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
Cache-Control: no-store            # 機密データを含むレスポンス
  • CORSは Access-Control-Allow-Origin: * を安易に使わず、許可するオリジンを明示する

参考


9. 冪等性と並行制御

冪等性キー

ネットワーク障害でクライアントがリトライしても二重処理されないよう、POST に冪等性キーを導入します(決済・注文などで特に重要)。

POST /payments
Idempotency-Key: 8e03978e-40d5-43e8-bc93-6894a57f9324
  • サーバーはキーと結果を一定期間保存し、同じキーの再送には 同じレスポンス を返す
  • 同じキーで異なるリクエスト内容が来たら 422 や 409 でエラーにする

楽観的ロック(ETag)

同時更新による上書き(Lost Update)を防ぎます。

GET /documents/42
→ ETag: "v7"
 
PUT /documents/42
If-Match: "v7"
→ 他者が先に更新していれば 412 Precondition Failed

10. キャッシュ

  • Cache-Control で明示的にキャッシュ方針を指定する
    Cache-Control: public, max-age=3600         # 公開データ
    Cache-Control: private, max-age=60          # ユーザー固有データ
    Cache-Control: no-store                     # 機密データ
  • ETag / Last-Modified で条件付きリクエストに対応し、変更がなければ 304 Not Modified を返す
    GET /products/1
    If-None-Match: "abc123"
    → 304 Not Modified
  • レスポンスが Accept-Language 等で変わる場合は Vary ヘッダを付ける

11. レート制限

  • 制限を超えたら 429 Too Many Requests と Retry-After を返す
  • 残り回数をヘッダで通知する
HTTP/1.1 429 Too Many Requests
Retry-After: 30
RateLimit-Limit: 100
RateLimit-Remaining: 0
RateLimit-Reset: 30
  • 制限単位(APIキー / ユーザー / IP)とウィンドウ(秒 / 分 / 日)をドキュメントに明記する
  • クライアント側には 指数バックオフ + ジッター でのリトライを推奨する

12. 非同期処理

時間のかかる処理(レポート生成、一括インポートなど)は同期で待たせず、ジョブとして扱います。

POST /reports
→ 202 Accepted
   Location: /jobs/789
 
GET /jobs/789
→ 200 OK
   { "id": "789", "status": "running", "progress": 45 }
 
GET /jobs/789
→ 200 OK
   { "id": "789", "status": "succeeded", "resultUrl": "/reports/321" }
  • 完了通知が必要な場合は Webhook を提供する
    • 署名(HMAC等)を付けて送信元を検証できるようにする
    • 受信側の失敗に備えてリトライし、イベントIDで重複排除できるようにする

13. ドキュメントと運用

ドキュメント

  • OpenAPI で仕様を記述し、ドキュメント・クライアントSDK・モックサーバーを生成する
  • 各エンドポイントにリクエスト / レスポンス例、エラー例、必要な権限を記載する
  • 変更履歴(Changelog)を公開する

監視・運用

  • すべてのリクエストに リクエストID を付与し、レスポンスヘッダでも返す(例:X-Request-Id)
  • 分散トレーシング(W3C Trace Context / OpenTelemetry)を導入する
  • レイテンシ、エラー率、スループットを監視し、SLOを定める
  • ヘルスチェック用エンドポイントを用意する(例:GET /health)
  • ログに個人情報やトークンを出力しない

テスト

  • 契約テスト(Contract Testing)でOpenAPI仕様と実装の乖離を検出する
  • 境界値・異常系(不正な入力、権限なし、存在しないID)のテストを書く

14. チェックリスト

設計レビュー時の確認用です。

URI・メソッド

  • URIは名詞・複数形・小文字ケバブケースになっている
  • ネストは2階層程度に収まっている
  • HTTPメソッドを意味どおりに使っている(GETで状態を変更していない)

レスポンス

  • 適切なステータスコードを返している
  • フィールドの命名規則が統一されている
  • 日時はISO 8601(タイムゾーン付き)
  • 金額に浮動小数点を使っていない
  • エラーは統一フォーマット(RFC 9457など)で返している

一覧取得

  • ページネーションがあり、limit に上限がある
  • フィルタ・ソートの仕様が一貫している

セキュリティ

  • HTTPSのみ
  • リソースごとに認可チェックをしている
  • トークンをURLに含めていない
  • 入力バリデーションとボディサイズ制限がある
  • レート制限がある

信頼性

  • 重要なPOSTに冪等性キーがある
  • 同時更新対策(ETag / If-Match)がある
  • 長時間処理は非同期化されている

運用

  • バージョニング方針と廃止ポリシーが決まっている
  • OpenAPI仕様書がある
  • リクエストIDでログを追跡できる

参考資料

0件のページ