FastAPI 教學 – Pydantic 模型中的 Field 驗證屬性(min_length, max_length, regex)
簡介
在使用 FastAPI 建立 API 時,資料驗證是不可或缺的環節。
FastAPI 之所以能快速且安全,核心在於 Pydantic:它會在請求進入路由前,自動將 JSON 轉換成 Python 物件,並依據模型定義執行驗證。
其中最常見的需求,就是對字串長度或格式做限制,例如 使用者名稱 必須在 3~20 個字元之間、密碼必須符合特定正規表達式 (regex)。如果不在模型層面先把這些規則寫好,後端就必須在每個 endpoint 手動檢查,既容易遺漏也會造成程式碼混亂。
本單元將深入說明 Pydantic 提供的 Field 驗證屬性 min_length、max_length、regex,並示範如何在 FastAPI 中結合 Request / Response 模型,讓 API 自動回傳清晰的錯誤訊息。文章適合 初學者 了解概念,也能給 中級開發者 提供實務上更乾淨、可維護的寫法。
核心概念
1. 為什麼使用 Field 而不是自行檢查?
- 統一管理:所有驗證規則集中在模型內,修改時只需要改一個地方。
- 自動文件化:FastAPI 會根據模型自動產生 OpenAPI 文件,前端開發者可以一眼看到欄位限制。
- 即時錯誤回報:請求資料不符合規則時,FastAPI 會回傳 422 Unprocessable Entity,且錯誤訊息已經說明是哪個欄位、哪個條件失敗。
2. Field 基本語法
from pydantic import BaseModel, Field
class UserCreate(BaseModel):
username: str = Field(..., min_length=3, max_length=20, description="使用者名稱")
password: str = Field(..., min_length=8, regex=r'^(?=.*[A-Z])(?=.*\d).+$')
...表示此欄位必填(等同於required=True)。min_length/max_length限制字串長度。regex接受正規表達式,用來驗證字串格式。description會被 OpenAPI 文件自動帶出,有助於 API 文件的可讀性。
3. 常見的驗證屬性
| 屬性 | 說明 | 範例 |
|---|---|---|
min_length |
最小字元數 | min_length=5 |
max_length |
最大字元數 | max_length=30 |
regex |
正規表達式驗證 | regex=r'^\d{4}-\d{2}-\d{2}$' (日期格式) |
alias |
欄位別名(前端可能使用不同名稱) | alias="user_name" |
title / description |
文件說明 | title="User Name" |
程式碼範例
以下示範 5 個實用範例,從最簡單的長度限制到結合多重條件與回傳模型。
範例 1️⃣ 基本長度驗證
# file: models.py
from pydantic import BaseModel, Field
class SimpleItem(BaseModel):
name: str = Field(..., min_length=2, max_length=10, description="商品名稱,2~10 個字元")
# file: main.py
from fastapi import FastAPI
from models import SimpleItem
app = FastAPI()
@app.post("/items")
def create_item(item: SimpleItem):
return {"msg": f"收到商品 {item.name}"}
當 name 少於 2 個字或超過 10 個字時,會回傳:
{
"detail": [
{
"loc": ["body", "name"],
"msg": "ensure this value has at least 2 characters",
"type": "value_error.any_str.min_length",
"ctx": {"limit_value": 2}
}
]
}
範例 2️⃣ 正則表達式驗證 Email
# file: models.py
from pydantic import BaseModel, Field
class EmailPayload(BaseModel):
email: str = Field(
...,
regex=r'^[\w\.-]+@[\w\.-]+\.\w{2,}$',
description="有效的 Email 地址"
)
# file: main.py
@app.post("/subscribe")
def subscribe(payload: EmailPayload):
return {"msg": f"已訂閱 {payload.email}"}
錯誤回應:
{
"detail": [
{
"loc": ["body", "email"],
"msg": "string does not match regex \"^[\\w\\.-]+@[\\w\\.-]+\\.\\w{2,}$\"",
"type": "value_error.str.regex"
}
]
}
範例 3️⃣ 密碼複雜度(多條件 regex)
class RegisterPayload(BaseModel):
username: str = Field(..., min_length=3, max_length=20)
password: str = Field(
...,
min_length=8,
regex=r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*]).+$',
description="至少 8 位,包含大小寫、數字與特殊字元"
)
技巧:使用「正向先行斷言」(
?=) 可以一次檢查多個條件,寫法較為簡潔。
範例 4️⃣ 結合 Response Model 與驗證訊息
class UserResponse(BaseModel):
id: int
username: str
created_at: str
@app.post("/users", response_model=UserResponse)
def create_user(payload: RegisterPayload):
# 假設寫入資料庫後取得 id 與時間
return UserResponse(id=1, username=payload.username, created_at="2025-11-20T12:34:56Z")
即使請求失敗,FastAPI 仍會回傳 422;成功時則只會回傳 UserResponse 定義的欄位,隱藏密碼等敏感資訊。
範例 5️⃣ 使用 alias 配合前端命名慣例
class ProfileUpdate(BaseModel):
display_name: str = Field(..., alias="displayName", min_length=1, max_length=30)
phone: str = Field(
None,
regex=r'^\+?[0-9]{7,15}$',
description="國際電話號碼,允許 + 開頭"
)
@app.patch("/profile")
def update_profile(data: ProfileUpdate):
return {"msg": f"已更新 {data.display_name}"}
前端傳的 JSON 會是
{ "displayName": "王小明" },Pydantic 會自動映射到display_name。
常見陷阱與最佳實踐
| 陷阱 | 可能原因 | 解決方式 |
|---|---|---|
| 正則表達式寫錯,導致所有輸入都被拒絕或全部通過 | 測試不完整、忘記 escape 字元 | 使用 re.compile() 先自行測試,或在 IDE 中開啟正則表達式即時驗證 |
忘記設定 ... 為必填,導致空值通過驗證 |
欄位預設是 None |
明確使用 Field(..., ...) 或在型別後加 ...(如 str = ...) |
max_length 超過資料庫欄位長度,寫入時產生 DB 錯誤 |
模型與資料庫定義不一致 | 把模型限制與資料庫 schema 同步,最好使用 Alembic 等 migration 工具 |
| 錯誤訊息過於冗長,前端難以呈現 | 直接回傳完整 detail 陣列 |
可自訂 exception_handler,只保留 loc、msg 兩個欄位給前端 |
使用 regex 時忘記加入 ^、$,導致子字串匹配 |
正則預設是「只要出現」就算成功 | 加上 ^、$ 確保全字串匹配,例如 r'^\d{4}$' |
最佳實踐
保持模型單一職責
- 請求模型只負責驗證輸入;回應模型只負責輸出。不要把兩者混在一起。
盡量使用
description、title- 這些屬性能自動寫入 OpenAPI,讓 Swagger UI 更友善。
正則表達式寫在常數檔
- 例如
constants.py中PASSWORD_REGEX = r'...',方便日後統一更改。
- 例如
測試驗證邏輯
- 使用
pytest搭配client.post("/endpoint", json=payload),確保 422 的錯誤訊息符合預期。
- 使用
避免過度驗證
- 只驗證必要條件,過度限制會影響使用者體驗。例如手機號碼若只需要長度檢查,別硬要加上國碼正則,除非真的有需求。
實際應用場景
1️⃣ 用戶註冊
- 需求:使用者名稱 4~16 個字元、密碼至少 8 位且必須包含大小寫、數字、特殊字元、Email 必須符合 RFC 標準。
- 實作:在
UserRegister模型中使用min_length、max_length、regex,並在路由返回UserResponse隱藏密碼。
2️⃣ 商品搜尋 API
- 需求:搜尋關鍵字最少 2 個字元,避免空字串或過短的請求浪費資源。
- 實作:
SearchQuery模型的keyword: str = Field(..., min_length=2),若不符合會直接回 422,後端不必額外檢查。
3️⃣ 表單驗證(前端+後端同時使用)
- 前端使用 React Hook Form,後端使用 FastAPI。
- 兩端共享同一套驗證規則(例如正則表達式),可以將正則寫在獨立的
constants.py,前端透過export const PASSWORD_REGEX = ...引入,保持一致性。
4️⃣ 多語系錯誤訊息
- FastAPI 內建的 422 訊息是英文。若要提供中文錯誤,可自訂 Exception Handler:
from fastapi import Request, FastAPI
from fastapi.responses import JSONResponse
from pydantic import ValidationError
app = FastAPI()
@app.exception_handler(ValidationError)
async def validation_exception_handler(request: Request, exc: ValidationError):
errors = [
{"欄位": err["loc"][-1], "訊息": err["msg"]} for err in exc.errors()
]
return JSONResponse(status_code=422, content={"錯誤": errors})
這樣前端拿到的錯誤資訊會是中文,更貼近使用者。
總結
- Pydantic 的
Field提供了直觀且功能強大的驗證屬性,讓 FastAPI 能在請求進入時即完成 字串長度 與 正則表達式 的檢查。 - 透過
min_length、max_length、regex,開發者可以在模型層級一次定義所有必要的資料限制,減少重複程式碼、提升可讀性與維護性。 - 配合
description、title、alias等補充屬性,API 文件會自動呈現完整規格,前端開發者也能直接在 Swagger UI 中看到限制條件。 - 注意常見的正則寫法、必填設定與資料庫長度同步問題,並善用 Exception Handler 轉換錯誤訊息,提供更友善的使用者體驗。
- 最後,將驗證規則抽離成常數檔、寫測試、保持模型單一職責,都是在實務專案中提升穩定性與可擴展性的關鍵。
掌握了這些技巧後,你的 FastAPI 專案將能更安全、更易維護,也能讓前後端在資料契約上保持高度一致,為後續功能擴充奠定堅實基礎。祝開發順利 🚀