本文 AI 產出,尚未審核
FastAPI 課程 – 靜態檔案與模板(Static & Templates)
使用模板實作登入頁與表單頁
簡介
在 Web 開發中,前端畫面與 後端 API 必須緊密合作,才能提供使用者完整的互動體驗。FastAPI 雖然以「高效能 API 框架」聞名,但它同樣支援 Jinja2 等模板引擎,讓我們可以直接在同一個專案裡呈現 HTML 頁面、處理表單提交、甚至管理靜態資源(CSS、JS、圖片)。
對於 登入頁、註冊表單、資料輸入頁 等常見需求,使用模板的好處包括:
- 分離關注點 – HTML 與 Python 程式碼分開管理,維護成本降低。
- 即時渲染 – 依據使用者狀態(如錯誤訊息、預設值)動態產生頁面。
- 快速開發 – 結合 FastAPI 的路由與依賴注入機制,表單驗證與回應可以在同一層級完成。
本篇文章將從 設定靜態檔案與模板目錄、建立登入表單、處理表單送出,一步步示範如何在 FastAPI 中完成完整的登入流程,並分享常見陷阱與最佳實踐,讓你能在實務專案中快速上手。
核心概念
1. 設定靜態檔案與模板目錄
FastAPI 內建 StaticFiles 與 Jinja2Templates 兩個工具,分別負責提供靜態資源(如 CSS、JS)與渲染 HTML 模板。
# main.py
from fastapi import FastAPI, Request, Form, status
from fastapi.responses import RedirectResponse, HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
app = FastAPI()
# 1️⃣ 讓 /static 路徑對應到 ./static 目錄
app.mount("/static", StaticFiles(directory="static"), name="static")
# 2️⃣ 設定模板目錄為 ./templates
templates = Jinja2Templates(directory="templates")
重點:
app.mount必須在所有路由之前呼叫,否則靜態檔案會被視為普通路由而產生 404。
2. 建立基本的登入表單模板
以下是一個簡單的 login.html,使用 Bootstrap 4 提供基礎樣式。模板放在 templates/login.html。
<!-- templates/login.html -->
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<title>登入 - FastAPI Demo</title>
<link rel="stylesheet" href="{{ url_for('static', path='css/bootstrap.min.css') }}">
</head>
<body class="bg-light">
<div class="container mt-5">
<h2 class="mb-4">登入系統</h2>
{% if error_msg %}
<div class="alert alert-danger">{{ error_msg }}</div>
{% endif %}
<form method="post" action="{{ url_for('login') }}">
<div class="form-group">
<label for="username">使用者名稱</label>
<input type="text" class="form-control" id="username" name="username"
value="{{ form_data.username | default('') }}" required>
</div>
<div class="form-group">
<label for="password">密碼</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<button type="submit" class="btn btn-primary">登入</button>
</form>
</div>
</body>
</html>
{{ url_for('static', path='css/bootstrap.min.css') }}:透過url_for產生靜態檔案的完整 URL,確保在不同部署環境下路徑正確。{{ error_msg }}、{{ form_data.username }}:由後端傳入的變數,用於顯示錯誤訊息或回填使用者先前輸入的資料。
3. 顯示登入頁的 GET 路由
# main.py(續)
@app.get("/login", response_class=HTMLResponse, name="login")
async def login_page(request: Request):
"""
渲染登入頁面,初始不帶任何錯誤訊息或表單資料。
"""
return templates.TemplateResponse(
"login.html",
{"request": request, "error_msg": None, "form_data": {}}
)
request必須傳入模板上下文,否則url_for會找不到request物件。name="login"為路由命名,方便在模板中使用url_for('login')產生表單 action。
4. 處理表單提交的 POST 路由
# main.py(續)
@app.post("/login", response_class=HTMLResponse)
async def login_submit(
request: Request,
username: str = Form(...),
password: str = Form(...)
):
"""
接收表單資料,驗證使用者身分,成功則導向首頁,失敗回傳錯誤訊息。
"""
# 這裡僅示範簡單的硬編碼驗證,實務上請使用資料庫 + 哈希密碼
if username == "admin" and password == "secret":
response = RedirectResponse(url="/", status_code=status.HTTP_302_FOUND)
# 設定簡易的 session cookie(示範用,實務上建議使用安全的 JWT / OAuth)
response.set_cookie(key="user", value=username, httponly=True)
return response
# 驗證失敗 → 重新渲染登入頁,帶入錯誤訊息與已輸入的使用者名稱
return templates.TemplateResponse(
"login.html",
{
"request": request,
"error_msg": "帳號或密碼錯誤,請再試一次。",
"form_data": {"username": username}
}
)
Form(...)讓 FastAPI 自動把application/x-www-form-urlencoded或multipart/form-data轉成參數。- 重要:若驗證失敗,不要直接回傳 401,而是重新渲染表單並顯示錯誤,這樣使用者體驗較好。
- 這裡示範了 RedirectResponse,在成功登入後導向首頁,同時寫入一個簡易的 cookie 作為登入狀態。
5. 建立受保護的首頁範例
# main.py(續)
@app.get("/", response_class=HTMLResponse)
async def home(request: Request, user: str = None):
"""
只要有 `user` cookie 即視為已登入,否則導回登入頁。
"""
if not request.cookies.get("user"):
return RedirectResponse(url="/login", status_code=status.HTTP_302_FOUND)
return templates.TemplateResponse(
"home.html",
{"request": request, "user": request.cookies.get("user")}
)
home.html(放在 templates/home.html):
<!-- templates/home.html -->
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<title>歡迎 {{ user }}</title>
<link rel="stylesheet" href="{{ url_for('static', path='css/bootstrap.min.css') }}">
</head>
<body class="bg-light">
<div class="container mt-5">
<h2 class="mb-4">Hello, {{ user }}!</h2>
<p>這是一個使用 FastAPI + Jinja2 渲染的簡易首頁。</p>
<a href="/logout" class="btn btn-secondary">登出</a>
</div>
</body>
</html>
6. 登出路由(清除 Cookie)
@app.get("/logout")
async def logout():
"""
清除登入 cookie,並導回登入頁。
"""
response = RedirectResponse(url="/login", status_code=status.HTTP_302_FOUND)
response.delete_cookie(key="user")
return response
常見陷阱與最佳實踐
| 常見問題 | 可能原因 | 解決方式 |
|---|---|---|
模板找不到 url_for('static', …) |
沒有在 TemplateResponse 中傳入 request |
確保 {"request": request, …} 皆傳入模板 |
| POST 表單送出 405 Method Not Allowed | 路由只宣告了 GET,未提供 POST |
為同一路徑同時寫 @app.get 與 @app.post,或使用 @app.api_route(..., methods=["GET","POST"]) |
| 表單驗證失敗後錯誤訊息不顯示 | 模板未正確使用 error_msg 變數 |
在 login.html 加入 {% if error_msg %} 區塊 |
| Cookie 不會被瀏覽器送出 | 設定了 domain、path 或 secure 不符合測試環境 |
測試時使用 httponly=True、secure=False(正式環境請改為 secure=True 並使用 HTTPS) |
| 靜態檔案載入慢或 404 | app.mount 的路徑或 StaticFiles 目錄錯誤 |
確認 static 目錄結構與 app.mount("/static", StaticFiles(directory="static")) 一致 |
最佳實踐
- 分層目錄:
project/ ├─ app/ │ └─ main.py ├─ templates/ │ ├─ login.html │ └─ home.html └─ static/ ├─ css/ │ └─ bootstrap.min.css └─ js/ - 使用 Pydantic 驗證表單(可用
Form+BaseModel)提升可讀性與可維護性。 - 安全性:
- 永遠使用 HTTPS。
- 密碼請儲存 bcrypt / argon2 雜湊值,切勿明碼儲存。
- 采用 JWT 或 OAuth2 替代簡易 cookie,避免 CSRF。
- 錯誤訊息國際化:將文字抽離成
i18n檔案,未來支援多語系更方便。 - 測試:使用
TestClient撰寫單元測試,確保 GET/POST 路由、重導向與 Cookie 行為正確。
# tests/test_auth.py
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
def test_login_success():
response = client.post("/login", data={"username": "admin", "password": "secret"})
assert response.status_code == 302
assert response.headers["location"] == "/"
# 檢查 cookie 是否被設定
assert "user=admin" in response.headers["set-cookie"]
實際應用場景
| 場景 | 為何使用模板 + 靜態檔案 | 相關程式碼片段 |
|---|---|---|
| 企業內部管理系統 | 需要快速打造登入、權限控制與統一 UI,且系統不一定要 SPA(單頁應用)。 | login.html + home.html + Cookie 驗證 |
| 教學平台 | 每個課程都有獨立的說明頁與練習表單,使用模板可以共用版面,提升維護效率。 | templates/course_{id}.html 動態渲染 |
| 原型開發 | 想在短時間內驗證概念,使用 FastAPI + Jinja2 能即時看到 UI 變化,無需額外前端框架。 | static/css/style.css 直接寫在 templates 中 |
| 混合式 API + 前端 | 部分路由回傳 JSON,部分路由回傳 HTML,FastAPI 原生支援兩者共存。 | @app.get("/api/users") 返回 JSON,@app.get("/login") 返回 HTML |
總結
- FastAPI 不僅是高效能的 API 框架,同時也能透過 Jinja2 與 StaticFiles 直接提供完整的 Web 前端功能。
- 透過本文的步驟,我們完成了 靜態資源掛載、模板渲染、表單送出與驗證、以及 簡易的登入/登出流程。
- 注意 request 物件 必須傳入模板、正確設定路由方法與靜態路徑,才能避免常見的 404/405 錯誤。
- 為了 安全 與 可維護性,建議在正式專案中使用 HTTPS、密碼雜湊、JWT,並將 UI 元件與驗證邏輯分離。
掌握了「使用模板作登入頁 / 表單頁」的核心技巧後,你可以快速擴展至 註冊、忘記密碼、個人設定 等更多功能,讓 FastAPI 成為 全端開發 的利器。祝開發順利,玩得開心! 🚀