FastAPI – 靜態檔案與模板(Static & Templates)
主題:HTML 回應組合
簡介
在 Web 開發中,HTML 是前端與使用者互動的核心,而 FastAPI 除了提供高效能的 JSON API,亦能輕鬆地渲染 HTML 模板,讓你同時擁有 API 與傳統網站的雙重能力。透過 static(靜態檔案)與 templates(模板),開發者可以把 CSS、JavaScript、圖片等資源與 Jinja2、Mako 等模板引擎結合,產生動態的 HTML 回應。
本單元將說明如何在 FastAPI 中組合 HTML 回應,從基本設定、範例程式碼,到常見陷阱與最佳實踐,幫助你快速上手、打造可維護的前後端整合專案。
核心概念
1. 為什麼要使用模板引擎?
- 分離關注點:HTML 結構與 Python 邏輯分開,讓設計師與後端工程師可以平行開發。
- 重複利用:使用 layout、block、include 等機制,避免重複撰寫相同的標頭、腳本或樣式。
- 動態資料注入:在渲染階段把資料(如 DB 查詢結果)傳入模板,產生符合使用者需求的頁面。
FastAPI 官方推薦 Jinja2 作為模板引擎,因為它簡潔、功能完整且與 FastAPI 的 TemplateResponse 配合順暢。
2. 設定靜態檔案與模板目錄
# main.py
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
app = FastAPI()
# 1️⃣ 設定靜態檔案根目錄(預設會掛載在 /static)
app.mount("/static", StaticFiles(directory="static"), name="static")
# 2️⃣ 設定模板目錄
templates = Jinja2Templates(directory="templates")
StaticFiles會自動處理GET /static/...的請求,回傳檔案內容。Jinja2Templates會把templates資料夾內的.html檔案載入,並提供TemplateResponse物件。
小技巧:在
templates中建立base.html作為所有頁面的基礎版型,之後的頁面只需extend這個基礎模板即可。
3. TemplateResponse 的使用方式
from fastapi import Request
from fastapi.responses import HTMLResponse
@app.get("/", response_class=HTMLResponse)
async def home(request: Request):
"""
首頁範例:傳入 request 與自訂資料給模板
"""
context = {
"request": request, # 必須傳入 request,讓模板能使用 url_for
"title": "FastAPI 首頁",
"items": ["FastAPI", "Starlette", "Pydantic"]
}
return templates.TemplateResponse("home.html", context)
request必須放在 context 中,Jinja2 才能使用{{ url_for('static', path='...') }}產生正確的靜態資源 URL。response_class=HTMLResponse讓 Swagger UI 仍能正確顯示回傳的 HTML(非必要,但可提升可讀性)。
4. 範例:多層模板與區塊(Blocks)
base.html(共用版型)
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<title>{% block title %}My Site{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', path='css/style.css') }}">
</head>
<body>
<header>
<h1>My Site</h1>
<nav>
<a href="{{ url_for('home') }}">Home</a>
<a href="{{ url_for('about') }}">About</a>
</nav>
</header>
<main>
{% block content %}{% endblock %}
</main>
<footer>
<p>© {{ now().year }} My Company</p>
</footer>
</body>
</html>
home.html(繼承 base)
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<h2>歡迎使用 FastAPI</h2>
<ul>
{% for item in items %}
<li>{{ item }}</li>
{% endfor %}
</ul>
{% endblock %}
說明:
extends讓home.html直接使用base.html的結構。block讓子模板只需要填入需要變動的部份,維護成本大幅降低。
5. 動態表單與 POST 回傳
from fastapi import Form, RedirectResponse
@app.get("/contact", response_class=HTMLResponse)
async def contact_form(request: Request):
return templates.TemplateResponse("contact.html", {"request": request})
@app.post("/contact")
async def submit_contact(
name: str = Form(...),
email: str = Form(...),
message: str = Form(...)
):
# 這裡可以把資料寫入資料庫或發送 Email
print(f"收到來自 {name} <{email}> 的訊息: {message}")
# 送出成功後導向感謝頁
return RedirectResponse(url="/thanks", status_code=303)
contact.html
{% extends "base.html" %}
{% block title %}聯絡我們{% endblock %}
{% block content %}
<h2>聯絡表單</h2>
<form action="/contact" method="post">
<label>姓名: <input type="text" name="name" required></label><br>
<label>Email: <input type="email" name="email" required></label><br>
<label>訊息:<br><textarea name="message" rows="5" required></textarea></label><br>
<button type="submit">送出</button>
</form>
{% endblock %}
重點:使用
RedirectResponse搭配 303 狀態碼,避免表單重送(Post/Redirect/Get)問題。
6. 結合 AJAX 與模板
有時候只想局部更新頁面,仍可保留模板的渲染優勢:
@app.get("/partial/item/{item_id}", response_class=HTMLResponse)
async def item_partial(request: Request, item_id: int):
# 假設從 DB 取得單筆資料
item = {"id": item_id, "name": f"商品 {item_id}", "price": 199}
return templates.TemplateResponse("partials/item.html", {"request": request, "item": item})
partials/item.html
<div class="item-card" id="item-{{ item.id }}">
<h3>{{ item.name }}</h3>
<p>價格:{{ item.price }} 元</p>
</div>
前端使用 jQuery(或 fetch)載入:
// static/js/app.js
function loadItem(id) {
fetch(`/partial/item/${id}`)
.then(res => res.text())
.then(html => {
document.querySelector('#item-container').innerHTML = html;
});
}
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方案 |
|---|---|---|
忘記把 request 放入 context |
模板內無法使用 url_for,靜態資源路徑會變成空字串。 |
必須在每一次 TemplateResponse 時加入 "request": request。 |
| 靜態檔案路徑錯誤 | 使用 {{ url_for('static', path='css/style.css') }} 時,path 必須是相對於 static 目錄的路徑。 |
檢查 static 目錄結構,建議在專案根目錄下建立 static/css、static/js、static/img。 |
| 模板快取導致舊資料 | Jinja2 會快取已編譯的模板,開發階段修改模板卻沒即時顯示。 | 在開發環境設定 templates.env.auto_reload = True,或在 FastAPI 啟動指令加上 --reload。 |
| POST 後直接回傳 HTML | 使用者刷新頁面會再次送出表單,產生重複資料。 | 使用 Post/Redirect/Get:回傳 RedirectResponse(303 或 302)。 |
| 大量資料一次渲染 | 把上千筆資料一次塞進模板會拖慢回應時間。 | 採用分頁、懶加載或 AJAX 部分渲染。 |
最佳實踐
- 統一管理模板變數:使用
context_processors(自行封裝)在每次渲染時自動注入共用變數(如site_name、now)。 - 分層目錄:
templates/layouts、templates/pages、templates/partials,讓檔案結構一目了然。 - 安全性:在模板中避免直接輸出使用者輸入,預設 Jinja2 會自動 HTML-escape,如需原樣輸出請使用
| safe,但務必確保資料已經過清理。 - 靜態資源版本化:在引用 CSS/JS 時加入 query string(例:
style.css?v=20251120)或使用StaticFiles的html=True讓瀏覽器快取失效,避免部署後舊資源卡住。
實際應用場景
| 場景 | 為何適合使用 FastAPI 的模板 | 示例功能 |
|---|---|---|
| 企業內部管理系統 | 需要快速的 API 與同時提供管理介面,開發成本低。 | 員工列表、權限設定、報表產生。 |
| 行銷活動單頁 | 輕量級的 Landing Page,結合表單收集 Leads。 | A/B 測試、即時統計、外部追蹤腳本。 |
| 文件產生器 | 依據資料庫內容產生 PDF/HTML 報告。 | 使用 WeasyPrint 把渲染好的 HTML 轉成 PDF。 |
| IoT 裝置監控儀表板 | 大量即時資料可透過 API 取得,同時提供圖形化儀表板。 | 使用 Chart.js、WebSocket 搭配模板渲染初始頁面。 |
| 教育平台 | 課程內容、測驗題目等需要動態渲染,同時提供 JSON API 給前端 SPA。 | 影片列表、作業上傳、即時評分。 |
總結
FastAPI 不僅是高效能的 JSON API 框架,也能透過 static 與 templates 兩大功能,輕鬆產出 HTML 回應。本文從設定、範例、常見陷阱到實務場景,提供了完整的概念與操作步驟,讓你在開發 動態網站、表單應用、混合式前後端 時,能夠:
- 正確掛載靜態資源與模板目錄
- 使用
TemplateResponse搭配request產生安全的 HTML - 利用 layout / block 讓版型可重用、維護成本降低
- 結合 AJAX、POST/Redirect/Get 等技術,提升使用者體驗
- 避免常見錯誤,遵循最佳實踐,確保專案長期可維護
掌握以上技巧後,你就能在 FastAPI 生態系中,快速構建出既具 API 能力又有完整前端呈現的全端應用。祝開發順利,期待看到你用 FastAPI 打造的精彩作品!