本文 AI 產出,尚未審核

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>&copy; {{ 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 部分渲染。

最佳實踐

  1. 統一管理模板變數:使用 context_processors(自行封裝)在每次渲染時自動注入共用變數(如 site_name、now)。
  2. 分層目錄:templates/layouts、templates/pages、templates/partials,讓檔案結構一目了然。
  3. 安全性:在模板中避免直接輸出使用者輸入,預設 Jinja2 會自動 HTML-escape,如需原樣輸出請使用 | safe,但務必確保資料已經過清理。
  4. 靜態資源版本化:在引用 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 打造的精彩作品!