本文 AI 產出,尚未審核
FastAPI 表單與檔案上傳 – 深入探索 Form 物件
簡介
在 Web 開發中,最常見的資料傳遞方式之一就是 HTML 表單。使用者在瀏覽器填寫表單後,會以 application/x-www-form-urlencoded 或 multipart/form-data 的方式送出資料。FastAPI 為了讓開發者能以 Pythonic 的方式處理這類請求,提供了 Form 這個依賴注入工具。
掌握 Form 的使用,不僅可以讓 API 接收傳統的表單欄位,還能與 File Upload、驗證、自訂型別 無縫結合。對於從事後端開發的同學來說,熟悉 Form 能大幅提升開發效率與程式可讀性,並確保資料安全性。
核心概念
1. 為什麼要使用 Form
- 自動驗證:FastAPI 會根據型別提示自動產生 Pydantic 驗證規則。
- 文件自動產生:
Form參數會被 Swagger UI 轉換成表單欄位,讓前端測試更直觀。 - 依賴注入:與
Depends、Header、Cookie等同層級的使用方式,保持 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 會把表單資料解析為 字串,若需要其他型別(如
int、bool)只要在參數上註明即可,FastAPI 會自動轉型。
3. 結合其他依賴
Form 可以與 Header、Cookie、Depends 同時使用,讓單一路由同時接受多種來源的資料。
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. 與檔案上傳結合
Form 與 File 常一起使用,形成「文字 + 檔案」的混合傳輸。
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(...) 表示必填,或給予預設值。 |
| 型別不匹配 | 表單資料全是字串,若直接寫 int、bool,FastAPI 會嘗試轉型,失敗會拋 422 錯誤。 |
在前端做好資料格式控制,或在後端使用 conint, confloat 等 Pydantic 限制。 |
多值欄位忘記 list |
同名欄位傳多個值,卻把參數寫成 str,只會收到最後一個值。 |
使用 list[str] 或 list[int]。 |
檔案與表單混用未加 multipart/form-data |
前端若以 application/x-www-form-urlencoded 送檔案,伺服器會解析失敗。 |
前端必須設定 enctype="multipart/form-data"。 |
| 安全性檢查缺失 | 直接把上傳的檔案寫入磁碟,可能遭受惡意檔案攻擊。 | 在接收後立即檢查檔案類型、大小,並使用安全的儲存路徑或雲端服務。 |
最佳實踐
- 明確宣告必填與預設值:讓 Swagger UI 更友善,也減少前端錯誤。
- 使用 Pydantic 驗證器:例如
constr(min_length=3)來限制字串長度。 - 分層處理:將表單資料的商業邏輯抽離到服務層,路由只負責接收與回傳。
- 統一錯誤回應:自訂
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": "註冊成功"}
實際應用場景
使用者登入/註冊
- 前端表單送出
username、password,後端使用Form直接驗證。
- 前端表單送出
搜尋與過濾
- 透過表單傳遞多個篩選條件(如
category[]、price_min),後端以list接收,快速組合 ORM 查詢。
- 透過表單傳遞多個篩選條件(如
上傳檔案 + 文字說明
- 例如部落格發文:
title、content為文字,cover_image為檔案,全部使用同一個 POST 端點。
- 例如部落格發文:
多步驟表單(Wizard)
- 每一步的資料都以
Form送出,後端暫存於 Session 或資料庫,最後一次性提交。
- 每一步的資料都以
IoT 裝置上傳感測資料
- 裝置以 HTTP POST 方式送出
device_id、timestamp、value,使用Form可快速建立測試 API。
- 裝置以 HTTP POST 方式送出
總結
Form 是 FastAPI 處理傳統 HTML 表單的核心工具,結合 型別提示、自動驗證、Swagger UI 生成,讓開發者只需專注於業務邏輯,而不必手動解析 request.form。
- 透過
Form(...)明確標示必填欄位,避免None錯誤。 - 使用
list、File、Depends等功能,可打造彈性且安全的混合型 API。 - 注意常見陷阱(型別不匹配、缺少
multipart/form-data等),依循最佳實踐(驗證、錯誤處理、分層設計),即可在 實務專案 中穩定、快速地處理表單與檔案上傳。
掌握了 Form,你就能在 FastAPI 上構建從簡單的登入表單到複雜的多媒體上傳系統,為使用者提供流暢且安全的互動體驗。祝開發順利!