本文 AI 產出,尚未審核

FastAPI 表單與檔案上傳 – 深入探索 Form 物件


簡介

在 Web 開發中,最常見的資料傳遞方式之一就是 HTML 表單。使用者在瀏覽器填寫表單後,會以 application/x-www-form-urlencodedmultipart/form-data 的方式送出資料。FastAPI 為了讓開發者能以 Pythonic 的方式處理這類請求,提供了 Form 這個依賴注入工具。

掌握 Form 的使用,不僅可以讓 API 接收傳統的表單欄位,還能與 File Upload驗證自訂型別 無縫結合。對於從事後端開發的同學來說,熟悉 Form 能大幅提升開發效率與程式可讀性,並確保資料安全性。


核心概念

1. 為什麼要使用 Form

  • 自動驗證:FastAPI 會根據型別提示自動產生 Pydantic 驗證規則。
  • 文件自動產生Form 參數會被 Swagger UI 轉換成表單欄位,讓前端測試更直觀。
  • 依賴注入:與 DependsHeaderCookie 等同層級的使用方式,保持 API 定義的一致性。

2. 基本語法

from fastapi import FastAPI, Form

app = FastAPI()

@app.post("/login")
async def login(username: str = Form(...), password: str = Form(...)):
    """
    接收使用者登入資訊,兩個欄位皆為必填。
    """
    return {"msg": f"歡迎 {username}!"}
  • Form(...) 中的 ... 表示 必填,若改為 Form(None) 則為可選。
  • 預設情況下,FastAPI 會把表單資料解析為 字串,若需要其他型別(如 intbool)只要在參數上註明即可,FastAPI 會自動轉型。

3. 結合其他依賴

Form 可以與 HeaderCookieDepends 同時使用,讓單一路由同時接受多種來源的資料。

from fastapi import Header, Depends

def get_token(x_token: str = Header(...)):
    return x_token

@app.post("/submit")
async def submit(
    title: str = Form(...),
    content: str = Form(...),
    token: str = Depends(get_token)
):
    # token 來自 Header,title & content 來自 Form
    return {"title": title, "content": content, "token": token}

4. 多值表單欄位(List)

HTML 多選或多個同名欄位會產生列表,FastAPI 只要把型別寫成 list 即可。

@app.post("/tags")
async def add_tags(tags: list[str] = Form(...)):
    # 前端會送出 tags=python&tags=fastapi...
    return {"tags": tags}

5. 與檔案上傳結合

FormFile 常一起使用,形成「文字 + 檔案」的混合傳輸。

from fastapi import File, UploadFile

@app.post("/profile")
async def update_profile(
    username: str = Form(...),
    avatar: UploadFile = File(...)
):
    # 讀取檔案內容
    content = await avatar.read()
    # 這裡僅示範,實務上請儲存至雲端或磁碟
    return {"username": username, "filename": avatar.filename, "size": len(content)}

6. 預設值與欄位描述

Swagger UI 會根據 Form 參數顯示說明文字與預設值。

@app.post("/search")
async def search(
    q: str = Form(..., description="搜尋關鍵字"),
    page: int = Form(1, description="頁碼,預設為 1")
):
    return {"q": q, "page": page}

常見陷阱與最佳實踐

陷阱 說明 解決方式
忘記 ... Form() 沒有傳入 ...,欄位會變成可選,導致意外的 None 錯誤。 明確使用 Form(...) 表示必填,或給予預設值。
型別不匹配 表單資料全是字串,若直接寫 intbool,FastAPI 會嘗試轉型,失敗會拋 422 錯誤。 在前端做好資料格式控制,或在後端使用 conint, confloat 等 Pydantic 限制。
多值欄位忘記 list 同名欄位傳多個值,卻把參數寫成 str,只會收到最後一個值。 使用 list[str]list[int]
檔案與表單混用未加 multipart/form-data 前端若以 application/x-www-form-urlencoded 送檔案,伺服器會解析失敗。 前端必須設定 enctype="multipart/form-data"
安全性檢查缺失 直接把上傳的檔案寫入磁碟,可能遭受惡意檔案攻擊。 在接收後立即檢查檔案類型、大小,並使用安全的儲存路徑或雲端服務。

最佳實踐

  1. 明確宣告必填與預設值:讓 Swagger UI 更友善,也減少前端錯誤。
  2. 使用 Pydantic 驗證器:例如 constr(min_length=3) 來限制字串長度。
  3. 分層處理:將表單資料的商業邏輯抽離到服務層,路由只負責接收與回傳。
  4. 統一錯誤回應:自訂 HTTPException,讓前端能統一處理驗證失敗。
from fastapi import HTTPException, status

@app.post("/register")
async def register(
    email: str = Form(..., regex=r'^\S+@\S+\.\S+$'),
    password: str = Form(..., min_length=8)
):
    if email_already_exists(email):
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="此 Email 已被註冊"
        )
    # 其餘註冊流程...
    return {"msg": "註冊成功"}

實際應用場景

  1. 使用者登入/註冊

    • 前端表單送出 usernamepassword,後端使用 Form 直接驗證。
  2. 搜尋與過濾

    • 透過表單傳遞多個篩選條件(如 category[]price_min),後端以 list 接收,快速組合 ORM 查詢。
  3. 上傳檔案 + 文字說明

    • 例如部落格發文:titlecontent 為文字,cover_image 為檔案,全部使用同一個 POST 端點。
  4. 多步驟表單(Wizard)

    • 每一步的資料都以 Form 送出,後端暫存於 Session 或資料庫,最後一次性提交。
  5. IoT 裝置上傳感測資料

    • 裝置以 HTTP POST 方式送出 device_idtimestampvalue,使用 Form 可快速建立測試 API。

總結

Form 是 FastAPI 處理傳統 HTML 表單的核心工具,結合 型別提示自動驗證Swagger UI 生成,讓開發者只需專注於業務邏輯,而不必手動解析 request.form

  • 透過 Form(...) 明確標示必填欄位,避免 None 錯誤。
  • 使用 listFileDepends 等功能,可打造彈性且安全的混合型 API。
  • 注意常見陷阱(型別不匹配、缺少 multipart/form-data 等),依循最佳實踐(驗證、錯誤處理、分層設計),即可在 實務專案 中穩定、快速地處理表單與檔案上傳。

掌握了 Form,你就能在 FastAPI 上構建從簡單的登入表單到複雜的多媒體上傳系統,為使用者提供流暢且安全的互動體驗。祝開發順利!