FastAPI 回應模型 (Response Model)

在 FastAPI 中,你可以像定義輸入參數一樣,定義 API 的回應模型 (Response Model)

這主要透過裝飾器參數 response_model 來達成。

基本用法

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None
    tags: list[str] = []

@app.post("/items/", response_model=Item)
async def create_item(item: Item):
    return item

雖然這裡輸入和輸出都是 Item,但你可以分開定義不同的模型。

為什麼需要 Response Model?

使用 response_model 有以下好處:

  1. 資料驗證:確保回傳的資料符合定義的結構。如果不符,伺服器會報錯 (500 Internal Server Error),而不是將錯誤的資料傳給客戶端。
  2. 資料過濾:這是最重要的功能。你可以過濾掉不應該回傳的敏感欄位 (如密碼)。
  3. 自動文件:Swagger UI 會顯示該 API 的回應結構。
  4. JSON 序列化:自動處理複雜型別 (如 datetime) 的轉換。

資料過濾範例 (常見應用)

假設你有一個 User 模型,包含密碼欄位,但你絕對不能把密碼回傳給前端。

你可以定義兩個模型:

  1. UserIn:用於註冊 (包含密碼)。
  2. UserOut:用於回應 (不含密碼)。
class UserIn(BaseModel):
    username: str
    password: str
    email: str
    full_name: Optional[str] = None

class UserOut(BaseModel):
    username: str
    email: str
    full_name: Optional[str] = None

# 輸入使用 UserIn
@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn):
    # 這裡可以做儲存到資料庫的操作...
    # 假設儲存後,我們直接回傳 user 物件 (此時 user 包含 password)
    return user

神奇的地方在於: 即使函數回傳了包含 passworduser 物件,FastAPI 會根據 response_model=UserOut 自動過濾UserOut 中沒有定義的欄位 (password)。

前端收到的回應只會有:

{
  "username": "user1",
  "email": "user1@example.com",
  "full_name": "User One"
}

這能有效防止敏感資料外洩。

response_model_exclude_unset

有時候,你希望只回傳「有設定值」的欄位,忽略那些預設值 (通常是 None)。

class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: float
    tax: float = 10.5
    tags: List[str] = []

items = {
    "foo": {"name": "Foo", "price": 50.2},
    "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
    "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}

@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
async def read_item(item_id: str):
    return items[item_id]
  • 對於 foodescriptiontaxtags 都使用預設值,所以回應中不會出現這些欄位。
  • 對於 bar:都有值,所以都會回傳。

這可以大幅減少 API 回應的 JSON 體積。

還有這些參數可以使用:

  • response_model_exclude_defaults=True
  • response_model_exclude_none=True

response_model_include 與 response_model_exclude

你也可以直接指定要包含或排除哪些欄位 (使用 setlist)。

@app.get(
    "/items/{item_id}/name",
    response_model=Item,
    response_model_include={"name", "description"}, # 只回傳這兩個欄位
)
async def read_item_name(item_id: str):
    return items[item_id]

@app.get(
    "/items/{item_id}/public",
    response_model=Item,
    response_model_exclude={"tax"}, # 排除 tax 欄位
)
async def read_item_public(item_id: str):
    return items[item_id]
建議優先使用 定義不同的 Pydantic Model (如 UserIn/UserOut) 的方式來做過濾,因為這在驗證和文件上更明確,也不容易出錯。include/exclude 比較適合簡單或臨時的過濾需求。

總結

  • 使用 response_model 來宣告 API 的輸出結構。
  • 這是保護敏感資料不外洩的最佳實踐。
  • 可以自動過濾掉多餘的欄位。
  • 支援 response_model_exclude_unset 等進階設定來最佳化 JSON 輸出。