FastAPI 課程 – 路由(Routing)
主題:查詢參數(Query Parameters)
簡介
在 RESTful API 中,除了路徑參數(Path Parameter)外,查詢參數(Query Parameters)是最常見的資料傳遞方式。它們被附加在 URL 後方(?key=value&...),用來過濾、分頁、排序或提供額外的選項。
FastAPI 以 型別提示 為核心,讓我們能夠在宣告函式簽名時直接定義查詢參數的型別、預設值與驗證規則,框架會自動完成解析、錯誤回應與 OpenAPI 文件生成。
掌握查詢參數的使用方式,不僅能寫出 易讀、可維護 的程式碼,還能提升 API 的使用體驗與安全性。以下內容將從概念說明、實作範例、常見陷阱到最佳實踐,完整呈現 FastAPI 中查詢參數的全貌,適合 初學者 迅速上手,也能讓 中階開發者 深入了解細節。
核心概念
1. 基本宣告 – 直接在函式參數中使用
FastAPI 會自動把 非路徑參數(即函式簽名中未以 {} 包住的參數)視為查詢參數。只要給予型別提示,框架即會從 request.query_params 取值。
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/")
def read_items(q: str):
"""
只要在 URL 中帶入 ?q=關鍵字,即可取得對應的字串。
例如:GET /items/?q=fastapi
"""
return {"query": q}
重點:
q為 必填 查詢參數,若未提供會回傳 422 錯誤。
2. 預設值 – 讓參數變為可選
若在參數後面加上預設值,FastAPI 會把它視為 可選,且在未提供時使用預設值。
@app.get("/items/")
def read_items(q: str = None):
"""
q 為可選參數,未提供時回傳 None。
"""
return {"query": q}
提示:使用
Optional[str](from typing import Optional)可以讓型別更清晰。
3. 型別驗證與自動轉換
FastAPI 會根據型別提示自動將字串轉成目標型別,並在轉換失敗時回傳 422 錯誤。
@app.get("/users/")
def read_users(skip: int = 0, limit: int = 10):
"""
- skip: 要跳過的筆數,預設 0
- limit: 回傳的筆數上限,預設 10
"""
return {"skip": skip, "limit": limit}
範例:
GET /users/?skip=5&limit=20→{"skip":5,"limit":20}
若skip=abc,則會得到驗證錯誤。
4. 列表參數 – 多值查詢
使用 list(或 Tuple)可以一次接收多個相同鍵名的值,FastAPI 會自動聚合成列表。
from typing import List
@app.get("/search/")
def search(tags: List[str] = []):
"""
tags 可接受多個 ?tags=python&tags=fastapi 的寫法。
"""
return {"tags": tags}
實例:
GET /search/?tags=python&tags=fastapi→{"tags":["python","fastapi"]}
5. 別名(Alias)與描述(Description)
有時候前端或第三方服務使用的參數名稱不符合 Python 命名規則,我們可以透過 Query 物件設定別名、預設值、說明與驗證條件。
from fastapi import Query
@app.get("/products/")
def list_products(
page: int = Query(1, ge=1, description="第幾頁,從 1 開始"),
size: int = Query(20, le=100, description="每頁筆數,最大 100"),
sort_by: str = Query(None, alias="sort", description="排序欄位")
):
"""
- page: 頁碼,必須 >=1
- size: 每頁筆數,最多 100
- sort_by: 以別名 sort 提供,若未給則不排序
"""
return {"page": page, "size": size, "sort_by": sort_by}
關鍵:
alias讓 API 文檔顯示sort,而程式內仍使用sort_by。
6. 依賴注入(Dependency Injection)與共用查詢參數
若多個路由需要相同的查詢參數(例如分頁資訊),可以建立 依賴,讓程式碼更 DRY。
from fastapi import Depends
def pagination_params(
skip: int = Query(0, ge=0),
limit: int = Query(10, ge=1, le=100)
):
return {"skip": skip, "limit": limit}
@app.get("/orders/")
def list_orders(pagination: dict = Depends(pagination_params)):
"""
透過 Depends 注入分頁參數。
"""
return {"orders": [], **pagination}
7. 高階驗證 – 正則、Enum、Custom Validator
FastAPI 支援 pydantic 的所有驗證功能,讓查詢參數也能使用 Enum、Regex 或自訂驗證。
import re
from enum import Enum
from pydantic import BaseModel, validator
class OrderStatus(str, Enum):
pending = "pending"
shipped = "shipped"
delivered = "delivered"
class SearchParams(BaseModel):
q: str
email: str
@validator("email")
def email_must_be_valid(cls, v):
if not re.fullmatch(r"[^@]+@[^@]+\.[^@]+", v):
raise ValueError("Invalid email format")
return v
@app.get("/orders/search/")
def search_orders(params: SearchParams = Depends()):
"""
使用 Pydantic Model 作為查詢參數,支援複雜驗證。
"""
return {"query": params.q, "email": params.email}
優點:所有驗證規則集中於
SearchParams,維護更方便。
常見陷阱與最佳實踐
| 陷阱 | 為什麼會發生 | 解決方式 |
|---|---|---|
| 未設定預設值導致參數變為必填 | 只寫 def foo(q: str):,若前端忘記傳參數會回 422 |
為非必要參數加 = None 或使用 Optional |
| 使用可變預設值(list、dict) | Python 預設值在函式定義時就被建立,會共享同一個物件 | 使用 Query([]) 或 field(default_factory=list) |
過度依賴 request.query_params |
手動解析會失去型別檢查與自動文件產生的好處 | 盡量使用函式參數或 Depends 注入 |
**忘記設定 alias**導致前端參數名稱不一致 |
API 要求 ?sort=price,卻寫成 sort_by |
使用 Query(..., alias="sort") |
| 未限制字串長度或數值範圍 | 可能收到惡意或不合理的請求,造成效能或安全問題 | 加入 ge, le, max_length 等限制 |
| 重複定義同名參數 | 同時在路徑與查詢中使用相同名稱,FastAPI 會優先路徑,可能產生混淆 | 保持命名一致或使用別名區分 |
最佳實踐
- 明確宣告必填與可選:使用
...(Ellipsis)或None讓意圖清楚。 - 使用
Query物件:一次設定預設值、別名、說明與驗證,提升自動文件品質。 - 集中驗證:若查詢參數較多,考慮使用 Pydantic Model 作為依賴,保持路由函式簡潔。
- 限制範圍:對於分頁、排序、搜尋字串長度等,設定合理的上下限,防止 DoS。
- 文件化:善用
description、example,讓前端開發者一眼看懂使用方式。
實際應用場景
1. 商品搜尋與過濾
@app.get("/catalog/")
def catalog(
q: str = Query(None, min_length=3, description="搜尋關鍵字"),
categories: List[int] = Query([], alias="cat"),
price_min: float = Query(0.0, ge=0),
price_max: float = Query(10000.0, le=10000),
sort: str = Query("relevance", regex="^(price|relevance|rating)$")
):
"""
- 支援關鍵字、類別多選、價格區間與排序方式。
- 前端可組合任意參數,後端只需處理已解析的 dict。
"""
# 假設有一個 service 層負責實際查詢
results = search_service.search(
keyword=q,
category_ids=categories,
price_range=(price_min, price_max),
order_by=sort,
)
return {"items": results}
2. 分頁 API(共用依賴)
def pagination(
page: int = Query(1, ge=1),
size: int = Query(20, ge=1, le=100)
):
return {"skip": (page - 1) * size, "limit": size}
@app.get("/blog/posts/")
def list_posts(pagination: dict = Depends(pagination)):
posts = blog_service.get_posts(skip=pagination["skip"], limit=pagination["limit"])
return {"page": pagination, "posts": posts}
3. 多語系內容切換
@app.get("/articles/")
def get_articles(lang: str = Query("en", regex="^(en|zh|ja)$")):
"""
根據 lang 參數回傳對應語言的文章。
"""
return article_service.fetch_by_language(lang)
總結
查詢參數是 FastAPI 中最靈活、最常用的資料傳遞方式。透過 型別提示、Query 物件與 Pydantic 驗證,我們可以:
- 簡潔宣告 必填、可選、預設值與別名
- 自動型別轉換與錯誤回應,減少手動檢查的負擔
- 集中驗證(Enum、Regex、Custom Model)提升安全性
- 共用依賴 讓分頁、過濾等功能維持 DRY 原則
- 即時產生完整 OpenAPI 文件,讓前端與測試團隊快速上手
只要遵循 預設值、範圍限制、別名與文件化 的最佳實踐,便能寫出 易讀、可維護且具備良好使用者體驗 的 API。希望本篇文章能幫助你在實務開發中,快速且正確地運用 FastAPI 的查詢參數,打造高品質的服務。祝開發順利 🎉