この記事の狙い
ガイド記事は丁寧ですが
実務では まず事故を避けたい というニーズが強いです
そこで本記事では
- よくある事故パターン
- 何が問題か
- どう直すか
- レビューで使えるチェックリスト
をセットでまとめます
読むだけでなく コピペしてチームのレビューテンプレにできます
想定読者
- APIを作る側 バックエンド フロントの両方
- 仕様が増えてAPIが崩れ始めたチーム
- なんとなく動くが 運用でつらくなってきた人
先に結論
- URLは名詞で表す 動詞を混ぜない
- エラーはコードと機械可読な形式で返す
- ページング ソート フィルタは規約で固める
- 冪等性と再送を最初から考える
- 互換性を壊す変更はバージョニングで制御する
アンチパターン集
動詞をURLに入れる
例
- POST /users/create
- POST /orders/cancel
何がまずいか
- HTTPメソッドの意味と二重化する
- 一貫性が崩れ 増えるほど命名が地獄になる
直し方
- POST /users
- POST /orders/{id}/cancellation
考え方
リソースを名詞で表し
操作はHTTPメソッドとサブリソースで表現します
ステータスコードが常に200
例
- 失敗しても200でerrorを返す
何がまずいか
- クライアントが成功か失敗かを正しく扱えない
- 監視やリトライの仕組みが壊れる
直し方の目安
- 400 入力不正
- 401 未認証
- 403 権限なし
- 404 リソースなし
- 409 競合
- 422 ビジネスルール違反
- 429 レート制限
- 500 サーバー内部
補足
APIゲートウェイやSDKがステータスコード前提で作られていることが多いです
まずは正しいステータスを返すだけで運用が楽になります
エラーレスポンスがバラバラ
例
エンドポイント毎にエラー形式が違い
messageだけだったり fieldが無かったりする
何がまずいか
- フロント実装が複雑化する
- ログと監視で集計できない
直し方
最低限 次を統一します
- code 機械可読なエラーコード
- message 人間向けの簡潔な説明
- details 入力エラーの配列など
- traceId 問い合わせ用ID
例
{
"code": "VALIDATION_ERROR",
"message": "入力に誤りがあります",
"details": [
{"field": "email", "reason": "invalid_format"}
],
"traceId": "01J..."
}
ページングが設計されていない
症状
- 一覧がlimitなし
- 途中からoffsetが重くなる
何がまずいか
- データが増えると突然遅くなる
- DBとAPIのコストが雪だるま式に上がる
直し方
- 小規模は limit offset
- 大規模や更新頻度が高いなら cursor pagination
カーソル型の考え方
- sortキーとして単調増加の値 例 createdAt id を使う
- 次ページは after=lastSeenKey で指定
ソート フィルタの規約がない
例
- sort=createdAt_desc
- order=desc&sortBy=createdAt
- filter=name:foo
何がまずいか
- エンドポイント毎に異なると覚えられない
- SDKや共通コンポーネントが作れない
直し方
次のように規約を決めると強いです
- sort=createdAt
- order=asc or desc
- filter[field]=value 形式
例
GET /users?filter[status]=active&sort=createdAt&order=desc&limit=20
互換性が壊れる変更を平気で入れる
例
- レスポンスのフィールド名を変更
- フィールドの型を変更
- 必須項目を追加
何がまずいか
- クライアントが即死する
- モバイルアプリの審査や配信の都合で復旧が遅れる
直し方
- 追加は基本的に安全 既存フィールドを残す
- 破壊的変更はバージョンを分ける
バージョニングの現実的な落とし所
- URLに v1 v2 を切る
- 重大変更の時だけ切る
- 期限を決めて古い版を廃止する
冪等性を考えていない
症状
- ネットワーク再送で二重注文
- タイムアウト後リトライで二重課金
何がまずいか
- 最悪の事故に直結する
直し方
- Idempotency-Keyをサポートする
- サーバー側でキー単位の処理結果を保存して再利用
例
POST /payments
Idempotency-Key: 9c7f...
注意
キーの保持期間や競合時の挙動も決めます
PUTとPATCHが曖昧
症状
- PUTが部分更新になっている
- PATCHが全更新になっている
何がまずいか
- クライアントが意図せずデータを消す
- 競合や差分更新が難しくなる
直し方の目安
- PUTはリソース全体の置き換え
- PATCHは部分更新
ただし 現実にはPUTを部分更新として統一するチームもあります
重要なのは どちらに寄せるかを決めてドキュメント化することです
日時とタイムゾーンが適当
症状
- 文字列だがフォーマットが曖昧
- ローカル時刻のまま返す
何がまずいか
- 集計やソートが壊れる
- 国際化で破綻する
直し方
- ISO 8601でUTCを返す
- 例 2025-12-25T10:30:00Z
大きすぎるレスポンス
症状
- 一覧で詳細まで全部返す
何がまずいか
- 体感が遅い
- モバイル回線で厳しい
- サーバーコストが上がる
直し方
- 一覧は要約だけ
- 詳細は個別取得
- expandパラメータで段階的に増やす
例
GET /orders?expand=items
認可が後付け
症状
- 認証はあるが認可がない
- userIdをパラメータでもらって信用する
何がまずいか
- 参照改ざん IDOR になる
直し方
- サーバー側で 誰が呼んだか をコンテキストとして持つ
- クライアントのuserId指定に依存しない
レート制限がない
症状
- 無制限に叩ける
何がまずいか
- 悪用やバグで落ちる
- リソースが枯渇する
直し方
- 429を返す
- クライアントにはRetry-Afterで待ち時間を返す
レビュー用チェックリスト
設計レビューやPRレビューで使えます
- URLが名詞になっている
- ステータスコードが妥当
- エラーフォーマットが統一されている
- ページングがある 一覧はlimit必須
- ソート フィルタ規約が守られている
- 破壊的変更の扱いが明確
- 冪等性が必要な操作はIdempotency-Keyを持つ
- 日時はUTC ISO 8601
- 一覧レスポンスが過剰でない
- 認可がサーバー側で完結している
- レート制限と429がある
- ログにtraceIdが残る
まとめ
REST APIは 最初は動けば勝ち に見えます
しかし 事故るポイントは毎回似ています
アンチパターンを先に潰しておくと
- 開発速度
- 運用負荷
- セキュリティ
の全部が楽になります
本記事のチェックリストをそのままチームの規約のたたき台にしてみてください