更新: 2026年03月 · 10 分で読める · 4,879 文字
RESTful APIの設計で押さえるべき5つのベストプラクティス
RESTful APIの設計には標準的なルールがあります。この記事では、実務で即座に適用できる5つのベストプラクティスを解説し、エンドポイント設計、ステータスコード、バージョニング、エラーハンドリングの具体例を通じて、メンテナンス性の高いAPI設計を実現する方法を学べます。
なぜRESTful APIのベストプラクティスが重要なのか
APIの設計品質は、開発チーム全体の生産性に直結します。曖昧なエンドポイント設計やステータスコードの使い分けの誤りは、バグの原因となり、ドキュメント作成の手間を増やし、フロントエンドチームとの連携に支障をきたします。RESTful APIのベストプラクティスを理解することで、スケーラブルで一貫性のあるAPI仕様を定義できます。
ベストプラクティス1:リソース指向の設計でエンドポイントを構成する
RESTful APIの基本は「リソース指向」です。動詞ではなく名詞(リソース)を中心にエンドポイントを設計することが、理解しやすく保守しやすいAPIの第一歩です。
良い例と悪い例
// ❌ 悪い例:動詞を含むエンドポイント
GET /api/getUsers
POST /api/createUser
PUT /api/updateUser/123
DELETE /api/deleteUser/123
// ✅ 良い例:リソース指向のエンドポイント
GET /api/users // ユーザー一覧取得
POST /api/users // ユーザー作成
GET /api/users/123 // 特定ユーザー取得
PUT /api/users/123 // ユーザー更新
DELETE /api/users/123 // ユーザー削除
// ✅ より複雑なリソース関係の例
GET /api/users/123/posts // ユーザー123の投稿一覧
POST /api/users/123/posts // ユーザー123に投稿を作成
GET /api/users/123/posts/456/comments // 特定投稿のコメント一覧
実装のハマりポイント
複数のリソース関係を設計する際、階層が深くなりすぎると問題が生じます。一般的には2階層までに留め、3階層目以降が必要な場合はクエリパラメータで対応します。
// ❌ 階層が深すぎる
GET /api/users/123/posts/456/comments/789/replies
// ✅ クエリパラメータで対応
GET /api/comments/789/replies
GET /api/posts/456?include=comments,author
ベストプラクティス2:適切なHTTPメソッドとステータスコードの使い分け
HTTPメソッドとステータスコードの正確な使い分けは、API利用者が直感的に動作を理解するためのシグナルとなります。
HTTPメソッドの役割分担
// リソース取得:GET(冪等性あり、キャッシュ可能)
GET /api/users/123
// レスポンス例:200 OK
// リソース作成:POST(冪等性なし)
POST /api/users
Content-Type: application/json
{
"name": "太郎",
"email": "taro@example.com"
}
// レスポンス例:201 Created
// Location: /api/users/124
// リソース更新:PUT(完全置換、冪等性あり)または PATCH(部分更新)
PUT /api/users/123
Content-Type: application/json
{
"name": "次郎",
"email": "jiro@example.com"
}
// レスポンス例:200 OK
// リソース削除:DELETE(冪等性あり)
DELETE /api/users/123
// レスポンス例:204 No Content
ステータスコードの使い分けテーブル
// 2xx 系:成功
200 OK // リクエスト成功
201 Created // リソース作成成功
204 No Content // 成功だが返却データなし(DELETE後など)
// 4xx 系:クライアント側のエラー
400 Bad Request // リクエスト形式が不正
401 Unauthorized // 認証が必要
403 Forbidden // 認可がない(認証済みだが権限不足)
404 Not Found // リソースが見つからない
409 Conflict // リクエストがサーバーの状態と競合
// 5xx 系:サーバー側のエラー
500 Internal Server Error
503 Service Unavailable
ベストプラクティス3:バージョニング戦略で破壊的な変更に対応する
APIの仕様が変わることは避けられません。重要なのは、既存のクライアントに影響を与えないバージョニング戦略を用意することです。
URLパスでのバージョニング(推奨)
// バージョンをURLパスに含める(最も明確)
GET /api/v1/users
GET /api/v2/users
// v1 と v2 で異なるレスポンス形式
// v1: ユーザー情報のみ
// v2: ユーザー情報 + メタデータ
クエリパラメータでのバージョニング
// バージョンをクエリパラメータで指定
GET /api/users?api_version=2
// Accept ヘッダを使用したコンテントネゴシエーション
GET /api/users
Accept: application/vnd.company.v2+json
非推奨ヘッダーの回避
カスタムヘッダー(例:X-API-Version)でバージョニングすることは避けてください。キャッシュサーバーがバージョンを正しく処理できないためです。URLパスが最も確実で、キャッシュメカニズムとも相性が良いアプローチです。
ベストプラクティス4:一貫性のあるエラーレスポンス形式を定義する
エラーが発生した際のレスポンス形式を統一することで、クライアント側での例外処理が簡潔になります。
標準的なエラーレスポンス形式
// エラーレスポンスの標準形式
{
"error": {
"code": "VALIDATION_ERROR", // マシン可読なエラーコード
"message": "入力値に誤りがあります", // ユーザー向けメッセージ
"status": 400, // HTTPステータスコード
"timestamp": "2025-01-15T10:30:00Z", // エラー発生時刻
"details": [ // 詳細情報(オプション)
{
"field": "email",
"message": "有効なメールアドレスを入力してください"
},
{
"field": "age",
"message": "18歳以上である必要があります"
}
]
}
}
// 実装例(Node.js Express)
app.post('/api/users', (req, res) => {
const { email, age } = req.body;
const errors = [];
if (!isValidEmail(email)) {
errors.push({
field: 'email',
message: '有効なメールアドレスを入力してください'
});
}
if (age < 18) {
errors.push({
field: 'age',
message: '18歳以上である必要があります'
});
}
if (errors.length > 0) {
return res.status(400).json({
error: {
code: 'VALIDATION_ERROR',
message: '入力値に誤りがあります',
status: 400,
timestamp: new Date().toISOString(),
details: errors
}
});
}
// 正常処理...
});
ベストプラクティス5:フィルタリング、ページネーション、ソートの標準化
大量のリソースを返すエンドポイントでは、クエリパラメータで柔軟な絞り込みが必須です。
実装例
// ページネーション
GET /api/users?page=2&limit=20
// ソート
GET /api/users?sort=created_at&order=desc
// フィルタリング
GET /api/users?status=active&role=admin
// 複数条件の組み合わせ
GET /api/users?page=1&limit=20&sort=created_at&order=desc&status=active&role=admin
// 実装例(Node.js Express)
app.get('/api/users', (req, res) => {
const {
page = 1,
limit = 20,
sort = 'created_at',
order = 'asc',
status,
role
} = req.query;
let query = User.query();
// フィルタリング
if (status) query = query.where('status', status);
if (role) query = query.where('role', role);
// ソート
query = query.orderBy(sort, order);
// ページネーション
const offset = (page - 1) * limit;
const users = query.offset(offset).limit(limit);
res.json({
data: users,
pagination: {
page,
limit,
total: query.resultCount()
}
});
});
実践的な設計チェックリスト
APIリリース前に確認すべき項目をまとめました。
// APIドキュメント作成時のチェックリスト
□ エンドポイントがすべてリソース指向か(動詞を含んでいないか)
□ HTTPメソッドが正しく使い分けられているか
□ ステータスコードが RFC 7231 に準拠しているか
□ エラーレスポンス形式が統一されているか
□ バージョニング戦略が明確に定義されているか
□ 認証・認可の仕組みが明記されているか
□ レート制限ポリシーが決まっているか
□ ページネーション、フィルタリングが必要なエンドポイントで実装されているか
□ CORS設定が適切か
□ APIキー、JWTなどの認証情報の取扱いが明記されているか
よくある質問
PUT は「リソース全体の置換」で、冪等性があります。一方、PATCH は「リソースの部分更新」であり、冪等性がない場合が多いです。実務では、ユーザーが一部のフィールドだけを更新したい場合は PATCH を、フロントエンド側で常にすべてのフィールドを送信する設計なら PUT を使うことが多いです。
最初から v1 でスタートすることをお勧めします。理由は、初期段階でバージョニングの仕組みを実装する方が、後から追加するより遥かに簡単だからです。最初からバージョニング戦略を組み込むことで、将来の破壊的な変更に対応しやすくなります。
RESTful APIはシンプルで標準的、キャッシング戦略が立てやすいのが利点です。一方、GraphQL は複数のリソース間の複雑なクエリが必要な場合に優れています。フロントエンドが多様なクライアント(Web、モバイル、etc)で異なるデータセットを必要とする場合は GraphQL が有効ですが、シンプルなCRUD操作が中心なら RESTful API で十分です。
まとめ
- エンドポイント設計は「動詞ではなく名詞」のリソース指向を採用する
- HTTPメソッド(GET、POST、PUT、DELETE)とステータスコードの使い分けを標準化する
- バージョニングはURLパスに含める方法が最も確実(/api/v1/、/api/v2/)
- エラーレスポンス形式を統一し、マシン可読なエラーコードを含める
- ページネーション、フィルタリング、ソートはクエリパラメータで