本文 AI 產出,尚未審核

FastAPI:混合 Path、Query 與 Body 參數的處理


簡介

在建構 RESTful API 時,路徑 (Path)查詢字串 (Query)請求主體 (Body) 三種參數類型各有其適用情境。
單一端點同時接受多種參數能讓介面更彈性,也更貼近實務需求,例如:根據資源 ID(Path)篩選結果(Query),同時接受使用者提交的 JSON 資料(Body)來完成複雜的業務邏輯。

FastAPI 以 型別提示 為核心,讓開發者可以在函式簽名中直接宣告 Path、Query、Body,框架會自動完成驗證、文件產生與錯誤回應。了解如何混合使用這三種參數,是從簡單 CRUD 進階到「多條件」或「批次寫入」等實務功能的關鍵。

本篇文章將從概念說明、程式碼範例、常見陷阱與最佳實踐,最後帶出實際應用場景,幫助你在 FastAPI 中熟練掌握混合參數的寫法。


核心概念

1️⃣ Path 參數

Path 參數是 URL 中固定位置的變數,必須使用 Path 裝飾器或在路由字串中以大括號 {} 標示。

from fastapi import FastAPI, Path

app = FastAPI()

@app.get("/users/{user_id}")
def read_user(user_id: int = Path(..., title="使用者 ID", ge=1)):
    """
    取得單一使用者資訊。
    - **user_id**: 必填,正整數
    """
    return {"user_id": user_id}
  • Path(...) 代表此參數為必填,ge=1 為「大於等於 1」的驗證條件。
  • FastAPI 會自動產生 OpenAPI 文件,說明此參數的型別與限制。

2️⃣ Query 參數

Query 參數位於 URL ? 後的鍵值對,適合用來 過濾、排序或分頁。使用 Query 來宣告,未給值時會以預設值或 None 取代。

from fastapi import Query

@app.get("/items/")
def list_items(
    q: str | None = Query(None, description="搜尋關鍵字"),
    limit: int = Query(10, ge=1, le=100, description="回傳筆數上限"),
    offset: int = Query(0, ge=0, description="分頁起始位置")
):
    """
    取得商品清單,可透過 q 進行搜尋,limit 與 offset 控制分頁。
    """
    # 假設此處呼叫資料庫取得結果
    return {"q": q, "limit": limit, "offset": offset}
  • None 表示參數是可選的。
  • gele 為數值範圍驗證,避免過大或過小的請求。

3️⃣ Body 參數

Body 參數常用於 POST、PUT、PATCH,傳遞 JSON、表單或檔案。FastAPI 推薦使用 Pydantic 模型來描述結構,提供自動驗證與文件產生。

from pydantic import BaseModel, Field

class ItemCreate(BaseModel):
    name: str = Field(..., max_length=50, description="商品名稱")
    price: float = Field(..., gt=0, description="商品價格")
    tags: list[str] = Field(default_factory=list, description="標籤列表")

@app.post("/items/")
def create_item(item: ItemCreate):
    """
    建立新商品,使用者必須提供 name、price,tags 為可選。
    """
    # 假設此處寫入資料庫
    return {"msg": "商品已建立", "item": item}
  • ... 代表必填欄位。
  • Field 可設定驗證條件、預設值與說明文字。

4️⃣ 同時混合使用 Path、Query 與 Body

下面的範例展示 三者同時出現在同一個端點 的寫法,常見於「更新指定資源,同時提供過濾條件與額外資訊」的情境。

from fastapi import Body, Path, Query

class UpdatePayload(BaseModel):
    title: str = Field(..., min_length=3)
    content: str | None = Field(None, description="可選的內容")

@app.put("/projects/{project_id}")
def update_project(
    project_id: int = Path(..., gt=0, description="專案 ID"),
    dry_run: bool = Query(False, description="是否僅模擬執行,不寫入資料庫"),
    payload: UpdatePayload = Body(..., description="更新資料")
):
    """
    更新指定的專案資訊。
    - **project_id**: 必填 Path 參數
    - **dry_run**: 可選 Query,若為 true 只回傳模擬結果
    - **payload**: 必填 Body,包含 title 與 optional content
    """
    if dry_run:
        return {"msg": "模擬更新成功", "project_id": project_id, "payload": payload}
    # 真正寫入資料庫的程式碼放這裡
    return {"msg": "更新完成", "project_id": project_id, "payload": payload}

重點說明

  • project_id 必須從路徑取得,且使用 Path 進行驗證。
  • dry_runQuery,預設為 False,讓使用者可以選擇僅做測試。
  • payload 直接以 Body 的 Pydantic 模型接收,保持資料結構的清晰。

5️⃣ 進階範例:批次操作 + 分頁

假設我們需要 一次上傳多筆商品,同時支援 分頁篩選條件,可以讓 Path、Query、Body 完全結合。

from typing import List

class BulkItem(BaseModel):
    name: str
    price: float

@app.post("/bulk/{category_id}/items")
def bulk_create_items(
    category_id: int = Path(..., description="商品類別 ID"),
    items: List[BulkItem] = Body(..., description="要建立的商品清單"),
    validate_only: bool = Query(False, description="僅驗證資料,不寫入")
):
    """
    批次新增商品,支援驗證模式與類別路徑。
    - **category_id**: Path,指定商品所屬類別
    - **items**: Body,JSON 陣列,每筆為 BulkItem
    - **validate_only**: Query,若為 true 只回傳驗證結果
    """
    if validate_only:
        # 只回傳成功驗證的筆數
        return {"valid_items": len(items)}
    # 真正寫入資料庫的程式碼
    created_ids = [i + 1 for i in range(len(items))]  # 假設回傳自增 ID
    return {"created_ids": created_ids, "category_id": category_id}

此範例同時展示:

  • Path 用於指向資源的父層(類別)。
  • Body 接收 List[BulkItem],讓一次請求可處理多筆資料。
  • Query 提供「僅驗證」的開關,讓前端先測試資料格式。

常見陷阱與最佳實踐

陷阱 說明 解決方式
參數名稱衝突 Path、Query、Body 中若使用相同名稱,FastAPI 會以最後一個為主,導致意外行為。 為不同來源的參數加上前綴(如 path_user_idq_user_id),或使用 alias 參數。
Body 必須唯一 一個端點只能有 一個 Body 參數,若需要多段資料,必須使用 Pydantic 模型或 Dict 包裝。 將多個資料結構整合進同一個模型,或使用 Union + Annotated(FastAPI 0.95+)。
Query 參數未預設值 若宣告為必填卻未提供,FastAPI 會回傳 422 錯誤,使用者體驗不佳。 為可選參數提供預設值或使用 Optional,並在程式內自行處理缺省情況。
驗證錯誤訊息過於冗長 預設的 422 回應會列出所有欄位的錯誤,前端可能只想要簡短訊息。 自訂 RequestValidationError 處理器,返回符合業務需求的錯誤格式。
過度使用 Path 把所有條件都塞進 Path,會讓 URL 變得不易閱讀且難以維護。 只把資源唯一標識放在 Path,過濾、排序等條件放在 Query。

最佳實踐

  1. 明確分工

    • Path → 唯一資源 ID。
    • Query → 過濾、分頁、旗標(如 dry_run)。
    • Body → 複雜結構或大量資料。
  2. 使用 Pydantic Model 來統一管理 Body,並在模型內使用 Field 定義驗證規則,讓文件自動同步。

  3. 為每個參數加入說明descriptiontitle),有助於自動產生的 Swagger UI 更具可讀性。

  4. 測試每種組合:利用 TestClient 撰寫單元測試,確保 Path、Query、Body 同時缺失或錯誤時的回應符合預期。


實際應用場景

場景:電商平台的「批次上架」功能

  • 需求:管理員選擇一個商品類別(Path),上傳包含多筆商品資訊的 JSON 檔案(Body),同時可以指定是否只做驗證(Query validate_only),以及是否只針對特定品牌過濾(Query brand)。
  • 實作:前述的 批次操作 + 分頁 範例即可直接套用,只需在 BulkItem 中加入 brand 欄位,並在路由函式內根據 brand 參數過濾。
@app.post("/bulk/{category_id}/items")
def bulk_create_items(
    category_id: int = Path(..., description="商品類別 ID"),
    items: List[BulkItem] = Body(...),
    brand: str | None = Query(None, description="限定品牌"),
    validate_only: bool = Query(False)
):
    # 依 brand 先過濾
    if brand:
        items = [i for i in items if i.brand == brand]
    # 後續驗證或寫入...

這樣的設計讓 API 同時支援路徑層級、條件過濾、以及大量資料寫入,符合企業級系統的彈性需求。


總結

  • FastAPI 允許在同一個端點同時使用 Path、Query、Body,只要在函式簽名中正確宣告並使用 PathQueryBody(或 Pydantic 模型)即可。
  • Path 應只承載唯一資源標識,Query 用於過濾、分頁或旗標,Body 用於傳遞結構化資料。
  • 透過 型別提示Field 驗證與 OpenAPI 自動文件產生,開發者可以快速建立安全、易維護的 API。
  • 注意避免參數名稱衝突、Body 只能有一個、以及適當提供預設值與說明,這是避免常見錯誤的關鍵。

掌握了混合參數的寫法後,你就能在 FastAPI 中構建更具彈性與實務價值的服務,從簡單 CRUD 走向複雜的批次處理與條件查詢,為你的後端開發增添更大的可能性。祝開發順利!