本文 AI 產出,尚未審核

FastAPI 教學 – Pydantic 模型中的 Field 驗證屬性(min_length, max_length, regex

簡介

在使用 FastAPI 建立 API 時,資料驗證是不可或缺的環節。
FastAPI 之所以能快速且安全,核心在於 Pydantic:它會在請求進入路由前,自動將 JSON 轉換成 Python 物件,並依據模型定義執行驗證。
其中最常見的需求,就是對字串長度或格式做限制,例如 使用者名稱 必須在 3~20 個字元之間、密碼必須符合特定正規表達式 (regex)。如果不在模型層面先把這些規則寫好,後端就必須在每個 endpoint 手動檢查,既容易遺漏也會造成程式碼混亂。

本單元將深入說明 Pydantic 提供的 Field 驗證屬性 min_lengthmax_lengthregex,並示範如何在 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,只保留 locmsg 兩個欄位給前端
使用 regex 時忘記加入 ^$,導致子字串匹配 正則預設是「只要出現」就算成功 加上 ^$ 確保全字串匹配,例如 r'^\d{4}$'

最佳實踐

  1. 保持模型單一職責

    • 請求模型只負責驗證輸入;回應模型只負責輸出。不要把兩者混在一起。
  2. 盡量使用 descriptiontitle

    • 這些屬性能自動寫入 OpenAPI,讓 Swagger UI 更友善。
  3. 正則表達式寫在常數檔

    • 例如 constants.pyPASSWORD_REGEX = r'...',方便日後統一更改。
  4. 測試驗證邏輯

    • 使用 pytest 搭配 client.post("/endpoint", json=payload),確保 422 的錯誤訊息符合預期。
  5. 避免過度驗證

    • 只驗證必要條件,過度限制會影響使用者體驗。例如手機號碼若只需要長度檢查,別硬要加上國碼正則,除非真的有需求。

實際應用場景

1️⃣ 用戶註冊

  • 需求:使用者名稱 4~16 個字元、密碼至少 8 位且必須包含大小寫、數字、特殊字元、Email 必須符合 RFC 標準。
  • 實作:在 UserRegister 模型中使用 min_lengthmax_lengthregex,並在路由返回 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_lengthmax_lengthregex,開發者可以在模型層級一次定義所有必要的資料限制,減少重複程式碼、提升可讀性與維護性。
  • 配合 descriptiontitlealias 等補充屬性,API 文件會自動呈現完整規格,前端開發者也能直接在 Swagger UI 中看到限制條件。
  • 注意常見的正則寫法、必填設定與資料庫長度同步問題,並善用 Exception Handler 轉換錯誤訊息,提供更友善的使用者體驗。
  • 最後,將驗證規則抽離成常數檔、寫測試、保持模型單一職責,都是在實務專案中提升穩定性與可擴展性的關鍵。

掌握了這些技巧後,你的 FastAPI 專案將能更安全、更易維護,也能讓前後端在資料契約上保持高度一致,為後續功能擴充奠定堅實基礎。祝開發順利 🚀