はじめに
前回の記事では、Pydanticを使った型バリデーションを解説しました。
FastAPIでAPIを作っていると、「データをどこで受け取るか」という場面に必ず直面します。
GET /users/42 ← 42 はどこ?
GET /users?page=2 ← page=2 はどこ?
POST /users + JSON本文 ← JSONはどこ?
この3つは 受け取る場所が違う ため、名前も使い方も異なります。
| 名前 | 場所 | 用途 |
|---|---|---|
| パスパラメータ | URLのパス部分 /users/{id}
|
リソースの特定 |
| クエリパラメータ | URLの ? 以降 ?page=2
|
絞り込み・ページング |
| リクエストボディ | HTTPリクエストの本文(JSON) | データの作成・更新 |
この記事では、それぞれの仕組みと使い分けの判断基準をコードと図で整理します。
1. パスパラメータ
基本の書き方
URLのパスに {変数名} を埋め込み、関数の引数に同じ名前を書くだけです。
from fastapi import FastAPI
app = FastAPI()
@app.get("/users/{user_id}")
def get_user(user_id: int):
return {"user_id": user_id}
-
GET /users/42→user_id = 42 -
GET /users/99→user_id = 99
型ヒントに int を書くだけで、FastAPIが自動で整数に変換します。
/users/abc のように整数に変換できない値が来た場合は 422 エラーになります。
複数のパスパラメータ
@app.get("/users/{user_id}/posts/{post_id}")
def get_user_post(user_id: int, post_id: int):
return {"user_id": user_id, "post_id": post_id}
-
GET /users/1/posts/5→user_id=1, post_id=5
注意:パスの順序
# ✖ 問題のある書き方
@app.get("/users/{user_id}")
def get_user(user_id: int): ...
@app.get("/users/me") # ← "me" が {user_id} に吸い込まれる!
def get_current_user(): ...
# 〇 固定パスを先に書く
@app.get("/users/me") # ← 先に定義
def get_current_user(): ...
@app.get("/users/{user_id}")
def get_user(user_id: int): ...
FastAPIはルートを上から順に評価するため、固定パスは動的パスより先に定義する必要があります。
2. クエリパラメータ
基本の書き方
URLパスに含まれない引数は、FastAPIが自動でクエリパラメータとして扱います。
@app.get("/users")
def get_users(page: int = 1, per_page: int = 10):
return {"page": page, "per_page": per_page}
-
GET /users→page=1, per_page=10(デフォルト値) -
GET /users?page=3→page=3, per_page=10 -
GET /users?page=2&per_page=5→page=2, per_page=5
任意パラメータ(Optional)
from typing import Optional
@app.get("/users")
def get_users(
page: int = 1,
per_page: int = 10,
keyword: Optional[str] = None # 省略可能
):
result = {"page": page, "per_page": per_page}
if keyword:
result["keyword"] = keyword
return result
-
GET /users→ keyword なし -
GET /users?keyword=Alice→keyword="Alice"で絞り込み
パスパラメータとクエリパラメータの組み合わせ
@app.get("/users/{user_id}/posts")
def get_user_posts(
user_id: int, # パスパラメータ
page: int = 1, # クエリパラメータ
published_only: bool = False # クエリパラメータ(bool型も使える)
):
return {
"user_id": user_id,
"page": page,
"published_only": published_only
}
-
GET /users/1/posts?page=2&published_only=true
→user_id=1, page=2, published_only=True
💡 boolのクエリパラメータ
FastAPIはtrue/false/1/0/on/offなどを自動でboolに変換してくれます。
3. リクエストボディ
基本の書き方
Pydanticモデルを引数の型ヒントに書くと、FastAPIは自動でリクエストボディから受け取ります。
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class UserCreate(BaseModel):
name: str
age: int
email: str
@app.post("/users")
def create_user(user: UserCreate):
return {"message": f"{user.name} を作成しました", "data": user}
リクエスト時にはJSONをボディに含めます:
POST /users
Content-Type: application/json
{
"name": "Alice",
"age": 30,
"email": "alice@example.com"
}
3つを同時に使う
3種類のパラメータは同時に使えます。FastAPIは引数の型から自動で判断します。
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Optional
app = FastAPI()
class PostCreate(BaseModel):
title: str
content: str
@app.post("/users/{user_id}/posts")
def create_post(
user_id: int, # パスパラメータ(URLに含まれる)
draft: bool = False, # クエリパラメータ(?draft=true)
post: PostCreate = None # リクエストボディ(JSON本文)
):
return {
"user_id": user_id,
"draft": draft,
"post": post
}
FastAPIの判定ルール:
URLパスに {変数名} がある → パスパラメータ
Pydanticモデルの型 → リクエストボディ
それ以外 → クエリパラメータ
4. 使い分けの判断基準
「どれを使えばいいか」迷ったときの判断フローです。
送りたいデータは何?
│
├─ 「どのリソースか」を特定する ID や名前
│ → パスパラメータ /users/{id}
│
├─ 「条件を絞る」ための補助情報(ページ・フィルター・並び順など)
│ → クエリパラメータ ?page=2&sort=name
│
└─ 「作成・更新」のための詳細データ(名前・住所・本文など)
→ リクエストボディ { "name": "...", "age": 30 }
具体例で確認
# 記事一覧(絞り込みあり)
GET /articles?category=tech&page=1&per_page=20
# → category, page, per_page は全部クエリパラメータ
# 記事1件取得
GET /articles/123
# → 123 はパスパラメータ
# 記事作成
POST /articles
Body: { "title": "...", "content": "...", "category": "tech" }
# → Body全体はリクエストボディ
# 記事更新
PUT /articles/123
Body: { "title": "新しいタイトル" }
# → 123 はパスパラメータ、Bodyはリクエストボディ
5. よくある間違いと対処法
間違い1:GETでリクエストボディを使う
# ✖ GETにボディは使えない(HTTP仕様的にNG)
@app.get("/users")
def get_users(filter: UserFilter): # Pydanticモデルをつけてしまっている
...
# 〇 GETの絞り込みはクエリパラメータで
@app.get("/users")
def get_users(name: Optional[str] = None, age: Optional[int] = None):
...
間違い2:パスパラメータの型を書き忘れる
# ✖ 型なし → 文字列として受け取ってしまう
@app.get("/users/{user_id}")
def get_user(user_id): # 型ヒントなし
return user_id # "42" (文字列)
# 〇 型を書く → 自動変換・バリデーションが効く
@app.get("/users/{user_id}")
def get_user(user_id: int):
return user_id # 42 (整数)
間違い3:クエリパラメータにデフォルト値を忘れる
# ✖ デフォルト値なし → 必須パラメータになる
@app.get("/users")
def get_users(page: int): # デフォルト値なし
...
# GET /users だけでアクセスすると 422 エラー
# 〇 デフォルト値を設定
@app.get("/users")
def get_users(page: int = 1):
...
6. まとめ:FastAPIの自動判定ルール
@app.post("/users/{user_id}/posts")
def create_post(
user_id: int, # ① URLパスに {user_id} → パスパラメータ
draft: bool = False, # ② Pydanticモデルでない + デフォルト値あり → クエリパラメータ
post: PostCreate, # ③ Pydanticモデル → リクエストボディ
):
...
FastAPIが引数を見て自動でルーティングしてくれるため、余計な設定が不要です。この判定ルールを覚えるだけで、ほとんどのAPIを迷わず実装できるようになります。
次回は FastAPI + SQLiteでCRUDアプリを作る を解説します。今回までの知識をベースに、実際にデータベースと繋いで「ユーザー管理API」を作り切ります。
参考
この記事は学習日記として書いています。間違いや補足があればコメントいただけると嬉しいです!