本文 AI 產出,尚未審核

FastAPI 路由(Routing) ── Response Model 自動轉換


簡介

在 RESTful API 開發中,回傳資料的結構 直接影響前端與其他服務的整合成本。若每一次都手動組裝 JSON,既容易出錯,也會讓程式碼變得雜亂。FastAPI 內建的 Response Model 機制,讓我們只需要定義 Python 的資料模型(通常使用 Pydantic),框架就會自動把回傳值 序列化 成符合模型的 JSON,並在開發階段提供型別檢查與自動文件產生。

本篇文章將說明什麼是 Response Model、它如何在路由中自動轉換、常見的使用情境與陷阱,並提供多個實作範例,協助你在專案中快速、正確地使用這項功能。


核心概念

1. 為什麼需要 Response Model?

需求 若不使用 Response Model 使用 Response Model 的好處
一致性 每個 endpoint 必須自行確保回傳欄位、型別正確 統一的模型保證所有回傳都遵循同一規範
型別安全 只能靠測試或手動檢查 Pydantic 於執行時即檢查資料型別,錯誤會拋出 ValidationError
自動文件 需要自行撰寫 OpenAPI/Swagger FastAPI 會根據模型自動產生文件,前端即能即時看到正確格式
維護成本 改動欄位需要在多處修改 只改模型即可,所有使用該模型的 endpoint 自動更新

2. 基本語法

在 FastAPI 中,路由的 @app.get/post/... 裝飾器接受 response_model 參數:

@app.get("/items/{item_id}", response_model=ItemResponse)
async def read_item(item_id: int):
    # 取得資料庫資料或其他來源
    item = get_item_from_db(item_id)
    return item            # 只要回傳 dict、Pydantic model 或相容物件即可
  • ItemResponse 必須是 PydanticBaseModel 子類別。
  • FastAPI 會在回傳前 自動執行序列化,只保留模型中定義的欄位,其他多餘欄位會被剔除(除非設定 response_model_exclude_unset=False)。

3. Pydantic Model 的基本寫法

from pydantic import BaseModel, Field
from typing import List, Optional

class ItemResponse(BaseModel):
    id: int = Field(..., description="商品唯一識別碼")
    name: str = Field(..., max_length=100, description="商品名稱")
    price: float = Field(..., gt=0, description="商品價格")
    tags: Optional[List[str]] = Field(default=None, description="商品標籤")
  • Field 用來描述欄位的驗證規則與說明,會直接映射到 OpenAPI 文件。
  • Optional 表示此欄位可為 null 或缺省。

4. Response Model 的自動轉換流程

  1. 路由函式回傳:可以是 dictlist、Pydantic model、ORM 物件(只要可被 jsonable_encoder 處理)。
  2. FastAPI 觸發 response_model:將回傳值傳給 Pydantic 的 parse_obj_as,進行 資料驗證欄位過濾
  3. 序列化:使用 jsonable_encoder 把資料轉為 JSON 可序列化的型別(如 datetime 會自動轉為 ISO8601 字串)。
  4. 回傳:最終的 HTTP 回應只包含模型中定義的欄位,並以 application/json 為 Content-Type。

程式碼範例

以下提供 5 個實用範例,涵蓋常見需求與技巧。

範例 1️⃣ 基本回傳模型

# file: main.py
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class HelloResponse(BaseModel):
    message: str
    version: str = "1.0"

@app.get("/hello", response_model=HelloResponse)
async def say_hello():
    # 直接回傳 dict,FastAPI 會自動轉成 HelloResponse
    return {"message": "Hello, FastAPI!"}

說明:即使回傳的 dict 少了一個 version 欄位,Pydantic 仍會使用模型的預設值填補。

範例 2️⃣ 回傳 ORM 物件(SQLAlchemy)

# file: models.py
from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class User(Base):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True, index=True)
    username = Column(String, unique=True, index=True)
    email = Column(String, unique=True, index=True)

# file: schemas.py
from pydantic import BaseModel

class UserResponse(BaseModel):
    id: int
    username: str
    email: str

    class Config:
        orm_mode = True   # 讓 Pydantic 能從 ORM 物件讀取屬性
# file: main.py (續)
from fastapi import Depends, HTTPException
from sqlalchemy.orm import Session
from . import models, schemas, database

@app.get("/users/{user_id}", response_model=schemas.UserResponse)
def read_user(user_id: int, db: Session = Depends(database.get_db)):
    user = db.query(models.User).filter(models.User.id == user_id).first()
    if not user:
        raise HTTPException(status_code=404, detail="User not found")
    return user          # 直接回傳 ORM 物件,因為 orm_mode=True 可自動轉換

註解orm_mode = True 讓 Pydantic 允許從非 dict 物件(如 SQLAlchemy 的模型)讀取屬性,這是使用資料庫模型時的必備設定。

範例 3️⃣ 回傳子集合與排除欄位

class Product(BaseModel):
    id: int
    name: str
    cost_price: float
    sale_price: float

    class Config:
        orm_mode = True

# 只想回傳給前端 id、name、sale_price,不要曝光成本價
@app.get("/products/{pid}", response_model=Product, response_model_exclude={"cost_price"})
def get_product(pid: int):
    product = fetch_product_from_service(pid)   # 回傳 Product ORM 物件
    return product

說明response_model_exclude 可以在回傳時排除特定欄位,避免敏感資訊外洩。

範例 4️⃣ 動態欄位(include / exclude

from fastapi import Query

@app.get("/items/{item_id}", response_model=ItemResponse)
def read_item(
    item_id: int,
    fields: Optional[str] = Query(None, description="用逗號分隔的欄位清單")
):
    item = get_item_from_db(item_id)
    if fields:
        include_set = set(fields.split(","))
        return item.dict(include=include_set)   # 只回傳指定欄位
    return item

技巧:利用 dict(include=...)dict(exclude=...) 搭配 response_model,可以在同一個 endpoint 實作 可自訂欄位的 API,減少前端多餘的資料傳輸。

範例 5️⃣ 自訂序列化(jsonable_encoder

from fastapi.responses import JSONResponse
from fastapi.encoders import jsonable_encoder
from datetime import datetime

class EventResponse(BaseModel):
    id: int
    name: str
    start_time: datetime

@app.get("/events/{eid}", response_model=EventResponse)
def get_event(eid: int):
    event = {
        "id": eid,
        "name": "開發者大會",
        "start_time": datetime(2025, 12, 1, 9, 30)   # datetime 需要被編碼
    }
    # 若要自行控制回傳格式,可先走 jsonable_encoder
    json_compatible = jsonable_encoder(event)
    return JSONResponse(content=json_compatible)

重點jsonable_encoder 會把 datetimeUUIDDecimal 等非 JSON 原生型別轉成字串或數字,確保回傳的 JSON 合法。


常見陷阱與最佳實踐

陷阱 說明 解決方案 / Best Practice
模型與實際回傳不一致 回傳的 dict 包含模型未定義的欄位,卻未使用 response_model_exclude_unsetexclude,導致意外資訊外洩。 使用 response_model_exclude_unset=True 或明確設定 excludeinclude
orm_mode 忘記設定 從 ORM 物件直接回傳卻未開啟 orm_mode,會拋出 ValueError: Object of type <Model> is not JSON serializable 在所有需要從 ORM 讀取的模型裡加上 class Config: orm_mode = True
大量資料一次回傳 response_model 會對每筆資料進行驗證,若列表過大會造成效能瓶頸。 使用分頁(limit/offset)或 StreamingResponse;對列表使用 response_model=List[ItemResponse] 並搭配 response_model_exclude_unset=True 減少驗證負擔。
自訂 jsonable_encoder 與模型衝突 手動編碼後再交給 FastAPI 仍套用 response_model,可能導致重複序列化或欄位遺失。 若已自行編碼,直接回傳 JSONResponse,或在 response_model 中使用 exclude_unset 來避免重複處理。
預設值與 None 混用 欄位設 Optional[...] = None,但在回傳時忘記提供值,會出現 null,前端若未處理會出錯。 明確在模型中設定 default 或在路由內使用 if value is None: del data["field"],或在 response_model_exclude_none=True

最佳實踐

  1. 模型即文件:所有公共 API 都應該有對應的 Pydantic BaseModel,不僅方便驗證,也能自動產生 Swagger。
  2. 最小化回傳:只回傳前端需要的欄位,使用 excludeinclude,減少網路流量。
  3. 統一錯誤格式:自訂全域例外處理器,將錯誤訊息包裝成符合模型的 JSON,保持 API 一致性。
  4. 測試模型:在單元測試裡直接呼叫 schema.parse_obj(),確保模型本身的驗證規則正確。
  5. 分層設計:將資料庫模型、業務邏輯、API 輸入/輸出模型分離,避免模型耦合造成維護困難。

實際應用場景

  1. 電商平台的商品 API

    • 商品資料庫表格包含成本價、庫存、供應商資訊。對外只需要回傳 id、name、sale_price、image_url。使用 response_model_exclude={"cost_price","supplier_info"},確保敏感資料不外洩。
  2. 即時聊天系統的訊息列表

    • 每筆訊息包含 id、sender_id、content、created_at、deleted_at。前端只需要顯示未刪除的訊息,且不需要 deleted_at。在路由中使用 response_model=MessageOut, response_model_exclude={"deleted_at"},同時搭配 limit/offset 分頁。
  3. 金融服務的交易紀錄

    • 交易模型包含 transaction_id、amount、currency、status、internal_notes。內部備註不應給客戶,利用 response_model_exclude={"internal_notes"},並在測試環境開啟 response_model_include_unset=False 以驗證欄位完整性。
  4. 多語系的說明文件 API

    • 使用 Optional[Dict[str, str]] 讓前端自行決定取哪個語系的文字,回傳時只保留 titlecontent,其餘欄位使用 exclude_none=True 避免傳空值。

總結

  • Response Model 是 FastAPI 提供的「模型即文件、模型即驗證」的核心功能,讓 API 回傳結構化、可預測且安全。
  • 只要定義好 Pydantic BaseModel,再於路由加上 response_model=YourModel,框架會自動完成 資料驗證、欄位過濾、序列化,同時產生完整的 OpenAPI 文件。
  • 在實務開發中,配合 orm_modeexclude/includeexclude_unsetexclude_none 等參數,可靈活控制回傳內容,避免敏感資訊外洩與過度傳輸。
  • 常見的陷阱包括忘記設定 orm_mode、未排除不必要欄位、一次回傳過大資料等,透過 最佳實踐(模型即文件、最小化回傳、分層設計、全域錯誤處理)可以有效降低風險。

掌握了 Response Model 的自動轉換 後,你的 FastAPI 專案將變得更乾淨、可維護,同時提供給前端與合作夥伴一致且可靠的 API 介面。祝開發順利,Happy coding!