FastAPI – 靜態檔案與模板(Static & Templates)
主題:Jinja2Templates 整合
簡介
在開發 Web 應用時,HTML 模板是將後端資料渲染成使用者可見頁面的關鍵。FastAPI 本身是一個以 ASGI 為基礎、支援非同步的高效能框架,雖然它的核心聚焦在 API 開發,但同時也提供了與 Jinja2 這類成熟模板引擎的無縫整合,讓開發者可以快速建立動態網頁。
本單元將說明如何在 FastAPI 中設定 靜態檔案(CSS、JS、圖片)與 Jinja2Templates,並透過實作範例展示從路由到模板渲染的完整流程。掌握這些技巧後,你就能在同一個專案裡同時提供 RESTful API 與傳統的網頁介面,提升開發效率與使用者體驗。
核心概念
1. 為什麼選擇 Jinja2?
- 表達力強:支援條件判斷、迴圈、過濾器等常見模板語法。
- 與 Python 完全相容:可以直接在模板中呼叫 Python 函式或變數。
- 廣泛使用:許多大型框架(如 Flask、Django)都採用 Jinja2,學會後可輕鬆遷移。
2. FastAPI 與 Jinja2 的整合方式
FastAPI 透過 fastapi.templating.Jinja2Templates 類別將 Jinja2 包裝起來,只要把模板目錄傳入即可。再配合 fastapi.staticfiles.StaticFiles,即可同時提供靜態資源與動態頁面。
from fastapi import FastAPI, Request
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
app = FastAPI()
# 1️⃣ 設定靜態檔案路徑(預設會掛載在 /static)
app.mount("/static", StaticFiles(directory="static"), name="static")
# 2️⃣ 設定模板目錄(預設是 /templates)
templates = Jinja2Templates(directory="templates")
小技巧:
StaticFiles必須在app.mount()之後才能被路由使用,否則會產生 404。
3. 基本的模板渲染流程
- 建立 Request 物件:FastAPI 會自動把
Request注入到路由函式中。 - 呼叫
templates.TemplateResponse:傳入模板檔名與context(字典),其中必須包含request鍵,讓 Jinja2 能取得 URL 生成器等資訊。 - 返回
Response:FastAPI 會自動將 HTML 回傳給瀏覽器。
@app.get("/", response_class=HTMLResponse)
async def read_root(request: Request):
# context 內可放任意資料供模板使用
context = {"request": request, "title": "歡迎使用 FastAPI"}
return templates.TemplateResponse("index.html", context)
4. 範例:從零建立靜態資源與模板
以下示範一個完整的目錄結構與程式碼,適合作為新手的起點。
my_fastapi_app/
│
├─ app.py # 主程式
├─ static/
│ ├─ css/
│ │ └─ style.css
│ └─ img/
│ └─ logo.png
└─ templates/
├─ base.html
└─ index.html
4.1 app.py
# app.py
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
app = FastAPI()
# 靜態檔案設定
app.mount("/static", StaticFiles(directory="static"), name="static")
# 模板設定
templates = Jinja2Templates(directory="templates")
@app.get("/", response_class=HTMLResponse)
async def home(request: Request):
"""
主要頁面,渲染 index.html 並傳入變數
"""
data = {
"request": request,
"title": "FastAPI + Jinja2 範例",
"user": {"name": "Alice", "age": 30},
"items": ["蘋果", "香蕉", "橘子"]
}
return templates.TemplateResponse("index.html", data)
4.2 templates/base.html(基礎版型)
{# templates/base.html #}
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<title>{% block title %}{{ title }}{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', path='css/style.css') }}">
</head>
<body>
<header>
<h1>{% block header %}FastAPI Demo{% endblock %}</h1>
</header>
<main>
{% block content %}{% endblock %}
</main>
<footer>
<p>© 2025 My FastAPI App</p>
</footer>
</body>
</html>
4.3 templates/index.html(實際渲染頁面)
{# templates/index.html #}
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block header %}
<img src="{{ url_for('static', path='img/logo.png') }}" alt="logo" height="50">
{{ title }}
{% endblock %}
{% block content %}
<h2>使用者資訊</h2>
<p>姓名:{{ user.name }}</p>
<p>年齡:{{ user.age }}</p>
<h2>水果清單</h2>
<ul>
{% for fruit in items %}
<li>{{ loop.index }}. {{ fruit }}</li>
{% else %}
<li>目前尚無資料</li>
{% endfor %}
</ul>
{% endblock %}
4.4 static/css/style.css(簡單樣式)
/* static/css/style.css */
body { font-family: Helvetica, Arial, sans-serif; margin: 0; padding: 0; }
header { background: #4CAF50; color: white; padding: 1rem; text-align: center; }
main { padding: 2rem; }
footer { background: #f1f1f1; text-align: center; padding: 0.5rem; }
說明:
url_for('static', path='...')是 FastAPI 為 Jinja2 提供的輔助函式,會自動產生正確的靜態檔案 URL。extends與{% block %}讓多頁面共享同一版型,維護成本大幅降低。
5. 進階範例:表單提交與資料回傳
以下示範如何使用 Jinja2 與 FastAPI 處理簡易的 POST 表單,並把使用者輸入回傳至同一頁面。
# app.py(接續前面的程式碼)
from fastapi import Form
@app.get("/contact", response_class=HTMLResponse)
async def contact_form(request: Request):
"""
顯示聯絡表單
"""
return templates.TemplateResponse("contact.html", {"request": request, "msg": ""})
@app.post("/contact", response_class=HTMLResponse)
async def submit_contact(
request: Request,
name: str = Form(...),
email: str = Form(...),
message: str = Form(...)
):
"""
處理表單送出,回傳感謝訊息
"""
# 這裡可以把資料寫入資料庫、發送 Email 等
thank_msg = f"感謝 {name}!我們已收到您的訊息。"
return templates.TemplateResponse(
"contact.html",
{"request": request, "msg": thank_msg, "name": name, "email": email, "message": message},
)
templates/contact.html
{# templates/contact.html #}
{% extends "base.html" %}
{% block title %}聯絡我們{% endblock %}
{% block content %}
<h2>聯絡表單</h2>
{% if msg %}
<p style="color: green;">{{ msg }}</p>
{% endif %}
<form method="post" action="{{ url_for('submit_contact') }}">
<label>姓名:<input type="text" name="name" required></label><br><br>
<label>Email:<input type="email" name="email" required></label><br><br>
<label>訊息:<textarea name="message" rows="5" required></textarea></label><br><br>
<button type="submit">送出</button>
</form>
{% endblock %}
關鍵點:
Form(...)讓 FastAPI 從application/x-www-form-urlencoded或multipart/form-data讀取表單欄位。url_for('submit_contact')會自動產生對應的路由 URL,避免硬編碼。
常見陷阱與最佳實踐
| 陷阱 | 可能的結果 | 解決方式 |
|---|---|---|
未將 request 放入 context |
Jinja2 內部的 url_for 失效,產生 UndefinedError |
在 TemplateResponse 的 context 中必須包含 {"request": request} |
| 靜態路徑寫錯 | 404 錯誤、樣式或圖片不顯示 | 使用 url_for('static', path='...') 產生路徑,避免手動拼字 |
| 模板檔案路徑不一致 | TemplateNotFound 錯誤 |
確認 Jinja2Templates(directory="templates") 指向正確資料夾,且檔名區分大小寫(Linux 上特別注意) |
| 在非同步路由中使用同步 I/O | 事件迴圈被阻塞,效能下降 | 若需讀寫檔案或 DB,使用 async 版本(如 aiofiles、asyncpg) |
| 過度在模板內寫 Python 邏輯 | 可讀性下降、難以維護 | 把複雜計算搬到路由或服務層,模板只負責呈現 |
最佳實踐
分層設計:
router處理請求與回應;service(或business)執行資料運算;templates僅負責渲染。
使用基礎版型(base.html):所有頁面都
extends基礎版型,讓全站樣式與腳本集中管理。靜態檔案快取:在
StaticFiles時加入cache_control參數,或在 CDN 上設定長效快取。app.mount( "/static", StaticFiles(directory="static", html=True, check_dir=False), name="static" )安全性:
- 防止 XSS:Jinja2 內建 HTML 轉義,除非使用
| safe,否則不會直接輸出 HTML。 - 只允許可信任的使用者上傳檔案,並在
static目錄之外儲存原始檔案。
- 防止 XSS:Jinja2 內建 HTML 轉義,除非使用
實際應用場景
| 場景 | 為何使用 Jinja2Templates |
|---|---|
| 後台管理介面 | 快速產生表格、分頁與搜尋結果,且可與同一個 FastAPI API 共用資料模型。 |
| 簡易部落格或文件站 | 文章內容以 Markdown 存在資料庫或檔案,渲染時以 Jinja2 組合版型,提供 SEO 友好的 HTML。 |
| 表單驅動的業務流程(如報名、聯絡) | 直接在 FastAPI 中處理表單驗證,回傳成功訊息或錯誤提示,減少前端框架依賴。 |
| 混合式微服務 | 某些微服務只提供 API,另一個服務負責 UI,兩者皆可使用同一套 FastAPI + Jinja2 架構,降低部署與維護成本。 |
| 即時報表 | 使用 async 讀取資料庫後,將結果傳給模板產生圖表(配合 Chart.js),在瀏覽器即時呈現。 |
總結
- Jinja2Templates 為 FastAPI 提供了完整且易用的模板渲染能力,讓開發者能在同一個框架內同時提供 API 與傳統的 HTML 網頁。
- 透過
StaticFiles與url_for,靜態資源的管理變得安全且可維護。 - 掌握 基礎版型、區塊(block)、以及 context 的傳遞技巧,可大幅提升開發效率與程式碼可讀性。
- 注意常見的路由、檔案路徑與安全性陷阱,並遵循 分層、快取、XSS 防護 等最佳實踐,才能在生產環境中發揮 FastAPI 的高效能優勢。
結合 FastAPI 的非同步特性與 Jinja2 的靈活模板語法,你現在已具備構建 高效、易維護且具備完整前後端功能 的 Web 應用所需的全部基礎。快去動手實作,將理論轉化為實際的產品吧! 🚀