REST APIエンドポイント設計:/active・/inactiveを分けるアンチパターンとフィルタリングパターン
概要¶
GET /api/v1/products/active と GET /api/v1/products/inactive という設計の問題点と、スケーラブルな代替パターンの解説。
詳細¶
問題のある設計¶
なぜ問題か:
-
ステータスが増えるたびにエンドポイントが増える
-
/activeはリソースか?アクションか?曖昧 - REST はリソース指向(名詞)
-
activeは状態(属性)であり、リソースではない -
複数条件でのフィルタができない
正しい設計:クエリパラメータでフィルタリング¶
# ステータスでフィルタ
GET /api/v1/products?status=active
GET /api/v1/products?status=inactive
GET /api/v1/products?status=archived
# 複数条件を組み合わせられる
GET /api/v1/products?status=active&category=electronics&sort=-created_at
# 複数ステータスを指定
GET /api/v1/products?status=active,pending
例外:サブリソースはパスに含める¶
# これは正しい(active が独立したリソースの場合)
GET /api/v1/orders/{id}/items # 注文の全アイテム
GET /api/v1/users/{id}/followers # ユーザーのフォロワー
# ステータスはあくまでフィルタ
GET /api/v1/orders?status=completed
実装側でのバリデーション¶
// Go での実装例
func GetProducts(w http.ResponseWriter, r *http.Request) {
status := r.URL.Query().Get("status")
// 許可されたステータス値のバリデーション
validStatuses := map[string]bool{
"active": true, "inactive": true, "archived": true,
}
if status != "" && !validStatuses[status] {
http.Error(w, "invalid status", http.StatusBadRequest)
return
}
// フィルタリングしてDBから取得
products, err := db.GetProducts(r.Context(), status)
...
}
なぜ重要か / いつ使うか¶
- 新しい API エンドポイントを設計するとき
- コードレビューで「このエンドポイント設計で良いか」を判断するとき
- 「RESTful な API とは?」の面接問題への回答の一部として