概要(リクエストモデルは「リクエストの設計図」)
リクエストモデルは、
「クライアントから送られてくる JSON が、どんな形・どんな型をしているべきかを表した“設計図”」
です。
FastAPI(+Pydantic)では、この「設計図」をクラスとして書いておくことで、
入力チェック(必須項目・型チェック)
自動ドキュメント(/docs に綺麗に表示)
エディタでの補完(.name など)
を一気に手に入れられます。
ここをちゃんと理解できると、
「なんとなく dict を受け取ってガチャガチャチェックするコード」から卒業して、
「型付きで安全な Web API」を作れるようになります。
なぜリクエストモデルが必要なのか(dict 直取りがつらくなる理由)
dict で直接受け取り始めると何が起こるか
リクエストボディ(JSON)を、何も考えずに dict で受け取ると、だいたいこうなります。
# 悪い意味で“ありがち”なコード
from fastapi import FastAPI, Request
app = FastAPI()
@app.post("/items")
async def create_item(request: Request):
data = await request.json()
name = data.get("name")
price = data.get("price")
if name is None or price is None:
return {"error": "name and price are required"}
# ここからさらに型チェックやら何やら…
return {"name": name, "price": price}
Python最初はこれでも動きますが、すぐに問題が出てきます。
どの項目が必須か、どの型か、コードを読まないと分からない
チェック漏れが起きやすく、バグりやすい
/ docs に自動で「このAPIはこういうJSONを受け取る」と出てこない
つまり、「仕様がコードの中に埋もれる」のがつらいポイントです。
モデルにする=「仕様を1か所にまとめて宣言する」
リクエストモデルを導入すると、この「仕様」をクラスとしてまとめて書けます。
仕様が一目で分かる(フィールド名・型・必須/任意・デフォルト)
その仕様どおりにリクエストが来なかったら、自動で 422 エラー
/ docs にそのまま表示される
「このAPIはこういう JSON を受け取ります」という契約を、
コードの中のあちこちではなく、「モデルクラス1つ」に集約できるのが本質です。
FastAPI+Pydantic でのリクエストモデルの基本
BaseModel を継承して「入力の形」をクラスで書く
FastAPI では、Pydantic の BaseModel を使ってリクエストモデル(スキーマ)を表現します。
最小例から見てみます。
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
in_stock: bool = True
@app.post("/items")
def create_item(item: Item):
return {
"message": "created",
"item": item
}
Pythonこのコードの意味を、丁寧に分解します。
Item クラスが「リクエストボディの設計図」です。name: str → name は必須の文字列price: float → price も必須の数値(float)in_stock: bool = True → in_stock は任意の真偽値(省略時は True)
エンドポイント側で item: Item と書いているのがポイントです。
FastAPI は、「このエンドポイントはリクエストボディを Item 型として受け取るんだな」と理解します。
クライアントは例えば次の JSON を送ります。
{
"name": "Apple",
"price": 1.5
}
in_stock を省略しても、デフォルト値 True が入ります。
FastAPI が裏側でやってくれていること
この item: Item と書くだけで、FastAPI+Pydantic が裏でやってくれることはかなり多いです。
JSON を Python の dict にパースする
dict を Item モデルに変換する
必須項目が全部あるかチェックする
型が正しいかチェックする(文字列に float を入れてないか、など)
エラーがあれば 422 (Unprocessable Entity) のレスポンスを返す
例えば、
{
"name": "Apple",
"price": "abc"
}
のような JSON を送ると、price が float に変換できないのでエラーになります。
自分で if 文を書かなくても、モデル定義通りに自動でバリデーションしてくれる。
ここがリクエストモデルの一番おいしいところです。
必須/任意/デフォルトの表現(ここをしっかり理解する)
必須フィールド(必ず入れてほしい項目)
Item モデルで、
class Item(BaseModel):
name: str
price: float
Pythonと書いた場合、name と price は必須です。
クライアントが次のように送ってきたら、
{
"name": "Apple"
}
price が欠けているので、FastAPI は 422 エラーを返します。
つまり、「型ヒントが書かれているフィールド」は、
デフォルト値を書かない限り「必須」とみなされます。
任意フィールド(あってもなくても良い項目)
任意にしたいときは、主に 2 通りの書き方があります。
ひとつは、デフォルト値を付ける方法。
class Item(BaseModel):
name: str
price: float
in_stock: bool = True
Pythonこの場合、
in_stock が省略されたら True
in_stock が指定されればその値(true / false)
になります。
もうひとつは、「本当に指定されないことがある(None も許す)」という意味の Optional です。
from typing import Optional
class Item(BaseModel):
name: str
price: float
description: Optional[str] = None
Pythonこれだと、
description を送ってもいいし、送らなくてもいい
送られなければ None
という扱いになります。
必須なのか、あってもなくてもいいのか、
なくてもいいなら「デフォルト値を持たせるか」「None を許すか」
このあたりを意識して設計すると、モデルがかなり洗練されます。
実用寄りの例:ユーザー登録 API のリクエストモデル
例:ユーザー登録に必要な情報をモデル化する
ユーザー登録 API を考えてみます。
欲しい情報は例えばこんな感じでしょうか。
ユーザー名(必須)
メールアドレス(必須)
年齢(任意)
お知らせメールを受け取るかどうか(任意、デフォルトは受け取る)
これを Pydantic モデルにすると、こう書けます。
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
from typing import Optional
app = FastAPI()
class UserCreate(BaseModel):
username: str
email: EmailStr
age: Optional[int] = None
allow_notifications: bool = True
@app.post("/users")
def create_user(user: UserCreate):
return {
"message": "user created",
"user": user
}
Pythonここで大事なところを深掘りします。
ひとつ目は、email に EmailStr を使っていることです。
Pydantic が提供する「メールアドレス用の型」です。
「@ が入っていない」「明らかにメールアドレスでない」文字列を弾いてくれます。
ふたつ目は、age を Optional[int] にしていること。
年齢は任意なので、指定されなければ None。
指定されれば int として扱われます。
みっつ目は、allow_notifications にデフォルト True を持たせていること。
送られなければ受け取る前提(True)、明示的に False を送ることもできます。
このモデルを見れば、「この API は何を受け取りたいのか」が一目で分かります。
コードを追いかけなくても、UserCreate だけを見れば仕様を把握できる。
これが「リクエストモデルに仕様を集約する」メリットです。
ネストしたリクエストモデル(住所などの複雑な構造)
モデルの中にモデルを入れる
現実の API では、単純な平べったい JSON だけでなく、
「中にオブジェクトを持つ JSON」もよく出てきます。
例えば、ユーザーの住所を別のモデルにして入れたいケース。
{
"username": "taro",
"email": "taro@example.com",
"address": {
"postal_code": "123-4567",
"prefecture": "Tokyo",
"city": "Koto-ku"
}
}
これを Pydantic モデルで表現してみます。
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class Address(BaseModel):
postal_code: str
prefecture: str
city: str
class UserCreate(BaseModel):
username: str
email: EmailStr
address: Address
@app.post("/users")
def create_user(user: UserCreate):
return {
"message": "user created",
"user": user
}
Pythonここでは、
Address モデルが「住所の仕様」
UserCreate の address: Address が、「ユーザーの中に住所を持つ」
ことを表しています。
FastAPI+Pydantic は、
外側の UserCreate だけでなく、中の Address についてもバリデーションしてくれます。
postal_code を省略したり、city に数値を渡したりするとエラーになります。
複雑な JSON ほど、モデルを分割してネストしてあげると読みやすくなります。
実務で効くポイント:リクエストモデルを「ドキュメント」として扱う
/docs にそのまま「仕様」が表示される
FastAPI アプリを起動して /docs を開くと、
エンドポイントごとに「Request body」としてモデルの構造が表示されます。
どんなフィールドがあるのか
必須か任意か
型は何か(string, number, boolean, object, array など)
が視覚的に確認できます。
これは、そのまま「API仕様書」として使えます。
モデルを少し直して再起動すれば、 /docs も自動で更新されます。
つまり、
仕様変更 = モデル定義の変更 = ドキュメントの自動更新
という流れになります。
仕様書だけ古い問題(あるある)を防げるのは、大きなメリットです。
IDE の補完と型チェックも効く
コードの中で、リクエストモデルを使うと、
エディタで型補完が効くようになります。
例えば、
@app.post("/items")
def create_item(item: Item):
total = item.price * 1.1
# item.name, item.in_stock などが補完される
Pythondict だと "name" や "price" を手で打ちますが、
モデルだと item. と打てば候補が出ます。
タイプミスで "prcie" と書いても気づきづらい dict 形式より、
モデル形式のほうがミスも減ります。
また、mypy などの型チェッカーを使うときも、
リクエストモデルが「ちゃんとした型」として扱われるので、
静的解析もしやすくなります。
まとめ(リクエストモデルは「安全で分かりやすい入力の入口」)
リクエストモデル(FastAPI+Pydantic)の本質を整理すると、こうなります。
リクエストモデルは、クライアントから送られる JSON の「形・型・必須・任意」をクラスとして表現した“仕様の設計図”。
BaseModel を継承したクラスにフィールド名と型ヒントを書くことで、入力チェック(バリデーション)を自動化できる。
必須項目はデフォルトなし、任意項目はデフォルト値や Optional で表現し、「何が必須で何がオプションか」がモデルだけで分かる。
ネストしたモデル(Address を User の中に持つなど)を使うことで、複雑な JSON も読みやすく・安全に扱える。
リクエストモデルは /docs にそのまま反映され、IDE 補完や型チェックも効くので、「コード=ドキュメント=バリデーション」という理想的な状態に近づける。

