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表示參數是可選的。ge、le為數值範圍驗證,避免過大或過小的請求。
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_run為 Query,預設為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_id、q_user_id),或使用 alias 參數。 |
| Body 必須唯一 | 一個端點只能有 一個 Body 參數,若需要多段資料,必須使用 Pydantic 模型或 Dict 包裝。 |
將多個資料結構整合進同一個模型,或使用 Union + Annotated(FastAPI 0.95+)。 |
| Query 參數未預設值 | 若宣告為必填卻未提供,FastAPI 會回傳 422 錯誤,使用者體驗不佳。 | 為可選參數提供預設值或使用 Optional,並在程式內自行處理缺省情況。 |
| 驗證錯誤訊息過於冗長 | 預設的 422 回應會列出所有欄位的錯誤,前端可能只想要簡短訊息。 | 自訂 RequestValidationError 處理器,返回符合業務需求的錯誤格式。 |
| 過度使用 Path | 把所有條件都塞進 Path,會讓 URL 變得不易閱讀且難以維護。 | 只把資源唯一標識放在 Path,過濾、排序等條件放在 Query。 |
最佳實踐:
明確分工:
- Path → 唯一資源 ID。
- Query → 過濾、分頁、旗標(如
dry_run)。 - Body → 複雜結構或大量資料。
使用 Pydantic Model 來統一管理 Body,並在模型內使用
Field定義驗證規則,讓文件自動同步。為每個參數加入說明(
description、title),有助於自動產生的 Swagger UI 更具可讀性。測試每種組合:利用
TestClient撰寫單元測試,確保 Path、Query、Body 同時缺失或錯誤時的回應符合預期。
實際應用場景
場景:電商平台的「批次上架」功能
- 需求:管理員選擇一個商品類別(Path),上傳包含多筆商品資訊的 JSON 檔案(Body),同時可以指定是否只做驗證(Query
validate_only),以及是否只針對特定品牌過濾(Querybrand)。 - 實作:前述的 批次操作 + 分頁 範例即可直接套用,只需在
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,只要在函式簽名中正確宣告並使用
Path、Query、Body(或 Pydantic 模型)即可。 - Path 應只承載唯一資源標識,Query 用於過濾、分頁或旗標,Body 用於傳遞結構化資料。
- 透過 型別提示、Field 驗證與 OpenAPI 自動文件產生,開發者可以快速建立安全、易維護的 API。
- 注意避免參數名稱衝突、Body 只能有一個、以及適當提供預設值與說明,這是避免常見錯誤的關鍵。
掌握了混合參數的寫法後,你就能在 FastAPI 中構建更具彈性與實務價值的服務,從簡單 CRUD 走向複雜的批次處理與條件查詢,為你的後端開發增添更大的可能性。祝開發順利!