FastAPI – 多值查詢參數(List[str])教學
簡介
在 RESTful API 中,查詢參數(Query Parameter)是最常見的過濾或搜尋方式。當使用者需要一次傳遞多個相同名稱的值(例如 ?tag=python&tag=fastapi),就會出現「多值查詢參數」的需求。FastAPI 內建對 list、set、tuple 等可迭代型別的支援,讓開發者只要在函式簽名上宣告 List[str],框架就會自動把 URL 中的多個值轉換成 Python 的列表。
正確掌握 多值查詢參數 的寫法,能讓 API:
- 更彈性:一次接受多個條件,不必為每個條件寫獨立的端點。
- 更易維護:統一的參數解析邏輯,減少重複程式碼。
- 提升使用者體驗:前端或第三方服務只要組合 URL 即可完成複雜篩選。
本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,帶你一步步熟悉 FastAPI 中的 List[str] 查詢參數,並探討實務應用情境。
核心概念
1. 為什麼使用 List[str] 而不是單一 str
單一字串只能傳遞一個值,若要同時搜尋多個關鍵字或標籤,就必須自行在程式裡切割字串(例如 "python,fastapi")。這樣的做法:
- 不符合 HTTP 標準:
?tag=python,fastapi把逗號視為字串的一部份,與?tag=python&tag=fastapi的語意不同。 - 可讀性差:使用者必須記得自行編碼分隔符號。
FastAPI 直接支援 同名多值,只要把參數型別寫成 List[str],框架會自動收集所有同名的 query string。
2. 基本語法
from typing import List
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(tags: List[str] = Query(None)):
"""
`tags` 會自動變成 List[str],若 URL 為
/items/?tags=python&tags=fastapi
則 tags == ["python", "fastapi"]
"""
return {"tags": tags}
Query(None)表示參數是可選的,若不傳遞則為None。- 若想讓參數必填,改成
Query(...)。
3. 預設值與驗證
FastAPI 允許在 Query 中設定預設值、最小/最大長度、正則表達式等驗證規則:
@app.get("/search/")
async def search(
keywords: List[str] = Query(
default=["fastapi"],
min_length=1,
max_length=20,
description="搜尋關鍵字,支援多值"
)
):
return {"keywords": keywords}
- 若使用者未提供
keywords,會自動使用["fastapi"]作為預設。 - 每個字串長度會受到
min_length、max_length的限制,超出會回傳 422 錯誤。
4. 與 Path、Body 參數的混用
查詢參數可以與路徑參數 (Path) 以及請求主體 (Body) 同時存在,只要在函式簽名中分別標註即可:
from fastapi import Path, Body
@app.post("/users/{user_id}/items/")
async def add_items(
user_id: int = Path(..., description="使用者 ID"),
tags: List[str] = Query(..., description="項目標籤"),
payload: dict = Body(..., description="其他資料")
):
# 這裡的 tags 仍是 List[str]
return {"user_id": user_id, "tags": tags, "payload": payload}
程式碼範例
以下提供 5 個實用範例,涵蓋從最簡單到進階的使用情境,並附上詳細註解說明。
範例 1️⃣:最簡單的多值查詢參數
# file: app.py
from typing import List
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/books/")
async def list_books(categories: List[str] = Query(None)):
"""
URL 範例:
/books/?categories=fiction&categories=history
-> categories == ["fiction", "history"]
"""
# 直接回傳收到的列表,實務上通常會放到資料庫查詢條件中
return {"categories": categories}
重點:只要在
Query中使用List[str],FastAPI 會自動把同名的 query string 合併成列表。
範例 2️⃣:設定必填與驗證規則
@app.get("/events/")
async def filter_events(
tags: List[str] = Query(
..., # 必填
min_length=2, # 每個字串最少 2 個字元
max_length=15, # 每個字串最多 15 個字元
description="活動標籤"
)
):
"""
URL 範例:
/events/?tags=music&tags=live
若任一 tag 長度不符合規則,會回傳 422 錯誤。
"""
return {"tags": tags}
技巧:使用
...讓參數變為必填,搭配min_length、max_length防止惡意或錯誤輸入。
範例 3️⃣:預設值 + 列舉型別 (Enum)
如果想限制只能傳入特定字串,可結合 Enum:
from enum import Enum
class Color(str, Enum):
red = "red"
green = "green"
blue = "blue"
@app.get("/products/")
async def get_products(
colors: List[Color] = Query(
default=[Color.red], # 預設只返回 red
description="可接受的顏色清單"
)
):
# FastAPI 會自動把字串轉成 Enum 成員
return {"colors": [c.value for c in colors]}
說明:若使用者提供
?colors=green&colors=yellow,yellow不是 Enum 成員,會回傳 422。
範例 4️⃣:結合 Path、Query、Body
這是一個真實的「新增商品」API,示範多值查詢參數與其他參數的混用:
from fastapi import Path, Body
class ItemCreate(BaseModel):
name: str
price: float
description: str | None = None
@app.post("/stores/{store_id}/items/")
async def create_item(
store_id: int = Path(..., description="店鋪編號"),
tags: List[str] = Query([], description="商品標籤,可多值"),
item: ItemCreate = Body(..., description="商品詳細資訊")
):
"""
- `store_id` 來自路徑
- `tags` 來自查詢參數,可為空列表
- `item` 來自請求體 (JSON)
"""
# 假設將資料寫入資料庫
saved = {
"store_id": store_id,
"tags": tags,
"item": item.dict(),
}
return {"message": "Item created", "data": saved}
要點:
tags若未提供,會得到空列表[],方便直接在資料庫查詢時使用IN條件。
範例 5️⃣:自訂分隔符號(逗號分割)
有時前端不方便一次傳多個同名參數,會改用逗號分割的單一字串。可以自行寫一個依賴函式將其轉成 List[str]:
from fastapi import Depends
def csv_to_list(q: str | None = Query(None)) -> List[str]:
"""
把 ?keywords=python,fastapi,async 轉成 ["python","fastapi","async"]
若 q 為 None,回傳空列表
"""
return q.split(",") if q else []
@app.get("/articles/")
async def search_articles(keywords: List[str] = Depends(csv_to_list)):
return {"keywords": keywords}
說明:使用
Depends可以把自訂的前置處理邏輯抽離,使路由函式保持乾淨。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
| 忘記給預設值 | List[str] 若未指定預設值,FastAPI 會把缺省的參數視為 None,導致後續需要 if tags is None 的判斷。 |
使用 Query([]) 或 Query(None, ...),根據需求決定是否接受 None。 |
| 重複名稱衝突 | 同一個路由同時使用 Path、Query、Body,若名稱相同會產生衝突。 |
為每個來源使用不同的變數名稱,或使用 alias 參數改變外部名稱(如 Query(..., alias="tag"))。 |
| 未驗證字串長度 | 大量或過長的字串會佔用過多資源,甚至造成 DoS。 | 加上 max_length、regex 或自訂驗證函式。 |
| 逗號分割的單一參數 | 前端傳 ?tags=python,fastapi 時,FastAPI 只會把整個字串視為單一元素。 |
使用 Depends 或自訂 Query(..., convert_underscores=False) 之類的依賴處理。 |
| Enum 轉換失敗 | 傳入不屬於 Enum 的值會直接回 422,訊息可能不夠友好。 | 自訂 HTTPException,或在 response_model 中提供更明確的錯誤說明。 |
最佳實踐
- 明確文件化:在
Query、Path、Body中使用description、example,讓自動產生的 OpenAPI 文檔清晰可讀。 - 使用型別提示:
List[str]、Set[int]、Tuple[float, ...]等型別不僅提升 IDE 補全,也讓 FastAPI 自動生成驗證。 - 預設空集合:若業務上允許「不傳」即視為「無條件」搜尋,建議使用
Query([]),避免在程式中處理None。 - 分層驗證:先在
Query中做基礎長度/正則驗證,再在業務層(service)中做更複雜的邏輯檢查。 - 測試多值情境:使用
TestClient撰寫單元測試,確保?tag=a&tag=b與?tag=a,b(自訂處理)都能得到預期結果。
實際應用場景
| 場景 | 需求 | 多值查詢參數的角色 |
|---|---|---|
| 商品搜尋 | 使用者可同時勾選多個類別、品牌、標籤 | categories=electronics&categories=home、brands=sony&brands=panasonic |
| 日誌檢索 | 管理員想一次查看多個服務的錯誤日誌 | services=auth&services=payment |
| 社交平台 | 文章或貼文支援標籤過濾 | tags=python&tags=fastapi |
| 統計報表 | 從多個地區、時間區間取得資料 | regions=us®ions=eu&months=2024-01&months=2024-02 |
| API 授權 | 客戶端可以一次傳入多個 API 金鑰以驗證不同資源 | api_keys=key1&api_keys=key2 |
實務建議:在資料庫查詢時,將收到的
List[str]直接映射為IN條件(SQLAlchemy、Tortoise‑ORM 等),可有效降低程式碼複雜度。例如stmt = select(Item).where(Item.category.in_(categories))
總結
- 多值查詢參數 是 API 設計中常見且重要的需求,FastAPI 只要在路由函式的參數上使用
List[str](或其他可迭代型別),就能自動將同名的 query string 合併為 Python 列表。 - 透過
Query可以設定 預設值、必填、長度驗證、Enum 限制,讓 API 更安全、文件更完整。 - 與 Path、Body 同時使用時,只要保持變數名稱唯一或使用
alias,就不會產生衝突。 - 常見陷阱包括忘記預設值、未驗證長度、逗號分割的單一參數等,遵循 最佳實踐(文件化、型別提示、空集合預設、分層驗證、測試)即可避免。
- 在實務上,從商品搜尋、日誌檢索到統計報表等場景,都能藉由
List[str]讓前端一次傳遞多個條件,後端僅需把列表直接套用於資料庫的IN查詢,既簡潔又高效。
掌握了 多值查詢參數 的寫法與注意事項後,你的 FastAPI 服務將變得更具彈性、更易維護,也能更快速地滿足日益複雜的業務需求。祝開發順利,Happy Coding! 🚀