REST API設計原則 - 変更に強いHTTP APIの作り方

12分 で読める | 2025.12.02

公式ドキュメント

REST APIでは、URL、HTTPメソッド、status code、bodyを一つの契約として設計します。

最初に決めるのはendpointの数ではなく、利用者が扱うリソースと、失敗・再試行・変更時の挙動です。

1. URLはリソースを表す

注文を扱うなら、URLには動詞ではなくリソース名を置き、操作はHTTPメソッドで表します。

ordersというresource nounにGET、POST、PATCH、DELETEのHTTP methodを対応させ、URLへaction名を並べないREST API設計図

GET    /orders
POST   /orders
GET    /orders/{orderId}
PATCH  /orders/{orderId}
DELETE /orders/{orderId}

POST /createOrderPOST /deleteOrderを増やすより、同じリソースへ標準メソッドを対応させると、clientやmiddlewareが意味を予測しやすくなります。

ただし、すべての業務操作をCRUDへ無理に押し込む必要はありません。「注文を確定する」が独立した状態遷移なら、確定というsub-resourceを作る方法があります。

POST /orders/{orderId}/confirmations

URLの単数・複数、case、ID形式は、既存API内で統一します。階層を深くしすぎず、子リソースが親なしでも識別できるなら/items/{itemId}のような短いURLも検討します。

2. HTTPメソッドの意味を守る

メソッド主な意味safeidempotent
GET表現を取得するはいはい
POSTcollectionへ作成・処理を依頼するいいえ標準では保証しない
PUT指定URIの表現を置き換えるいいえはい
PATCH一部を変更するいいえpatch形式と実装による
DELETE指定URIとの関連を削除するいいえはい

RFC 9110のidempotentは、同じrequestを複数回送った時、serverへ意図した効果が一回と同じになる性質です。ログが複数行残るなど、付随する処理が完全に一回になるという意味ではありません。

GETでデータ変更を行うと、crawler、prefetch、cacheが意図せず実行する危険があります。読み取り用メソッドに副作用を持たせません。

3. status codeとエラーbodyを分ける

status codeはHTTP共通の大分類、bodyはアプリ固有の詳細を伝えます。

状況代表的なstatus
取得成功200 OK
作成成功201 Created
bodyなしで成功204 No Content
入力形式が不正400 Bad Request
認証が必要または無効401 Unauthorized
認証済みだが許可されない403 Forbidden
リソースがない404 Not Found
現在状態と競合する409 Conflict
処理できない入力422 Unprocessable Content
requestが多すぎる429 Too Many Requests
server内部の失敗500 Internal Server Error

すべての失敗を200と独自success: falseで返すと、HTTP client、監視、retry判断が使いにくくなります。

Problem Detailsを使う

RFC 9457は、HTTP APIの機械可読なエラー形式を定義します。media typeはapplication/problem+jsonです。

{
  "type": "https://api.example.com/problems/insufficient-stock",
  "title": "在庫が不足しています",
  "status": 409,
  "detail": "注文数3に対して在庫は2です",
  "instance": "/orders/order_123"
}

typeはproblem種類を識別するURI、titleは種類の短い説明、detailは今回の発生内容です。stack trace、SQL、token、内部host名を返しません。入力項目ごとのerrorを拡張memberとして足す場合も、schemaを文書化します。

4. 後方互換性を先に考える

API clientはserverと同時に更新されるとは限りません。次の変更は一般に壊れやすい変更です。

  • fieldの削除・名前変更・型変更
  • 必須入力の追加
  • enumへ、古いclientが処理できない値を追加
  • status codeやエラー形式の変更
  • paginationや並び順の既定変更

新fieldの追加も、clientが未知fieldを拒否する設計なら壊れます。client側は不要なfieldを無視し、server側は移行期間に新旧形式を扱えるようにします。

versionはURLの/v1/、header、media typeなどで表せます。方式より重要なのは、何を一つのversion境界とし、古いversionをいつまで提供し、利用状況をどう測って終了するかです。

GET /v1/orders/order_123

小さな追加ごとにversionを増やさず、互換な追加と破壊的変更を分けます。廃止予定は文書、response header、利用者への通知で伝え、移行確認前に旧versionを消しません。

5. retryされるrequestを安全にする

通信切断時、clientは「serverが処理する前に失敗した」のか「処理後にresponseだけ失った」のか分かりません。PUTやDELETEはHTTP上idempotentになるよう設計します。

作成用POSTを安全にretryしたい場合、idempotency keyをAPI契約として導入できます。

POST /payments
Idempotency-Key: 8f8614a0-7c36-4cb8-9a88-7c69978cc92f
Content-Type: application/json

{"orderId":"order_123","amount":5000}

serverは認証主体、endpoint、key、request内容を結び付け、同じkeyと同じ内容の再送には最初の結果を返します。同じkeyで内容が違うrequestは拒否します。

keyの保存期間、同時requestの競合、失敗結果を保存するか、下流処理まで一回にする方法を決めます。headerを受け取るだけでは冪等性になりません。DBのunique制約やtransaction、外部決済側のidempotency機能と組み合わせます。

最小の設計チェック

リソース

  • URLから対象が分かるか
  • 認可をresourceごとにserver側で確認するか
  • actionを無理にGETへ置いていないか

エラー

  • status codeとproblem typeが一貫しているか
  • clientがretry可能か判断できるか
  • 秘密情報や内部実装を返していないか

変更

  • 古いclientが新responseを読めるか
  • 破壊的変更のversion、移行期間、終了条件があるか
  • 利用中の旧versionを観測できるか

再試行

  • timeout後の再送で二重作成・二重課金しないか
  • idempotency keyとrequest内容を照合するか
  • 同時実行でも一回の効果にできるか

認証、rate limit、pagination、OpenAPIは重要ですが、個別の設計テーマです。最初にresource、error、version、idempotencyの契約を固めてから追加します。

まとめ

変更に強いREST APIは、リソースをURL、操作をHTTPメソッドで表し、status codeとProblem Detailsで失敗を伝えます。clientを同時更新できない前提で後方互換性を守り、通信失敗によるretryを冪等に処理します。endpoint一覧より先に、失敗と変更の契約を設計することが重要です。

参考リソース

次に読む記事

← 一覧に戻る
PR
PR
PR
PR