本文 AI 產出,尚未審核

FastAPI – 靜態檔案與模板(Static & Templates)

主題:HTML 回應組合


簡介

在 Web 開發中,HTML 是前端與使用者互動的核心,而 FastAPI 除了提供高效能的 JSON API,亦能輕鬆地渲染 HTML 模板,讓你同時擁有 API 與傳統網站的雙重能力。透過 static(靜態檔案)與 templates(模板),開發者可以把 CSS、JavaScript、圖片等資源與 Jinja2、Mako 等模板引擎結合,產生動態的 HTML 回應。

本單元將說明如何在 FastAPI 中組合 HTML 回應,從基本設定、範例程式碼,到常見陷阱與最佳實踐,幫助你快速上手、打造可維護的前後端整合專案。


核心概念

1. 為什麼要使用模板引擎?

  • 分離關注點:HTML 結構與 Python 邏輯分開,讓設計師與後端工程師可以平行開發。
  • 重複利用:使用 layoutblockinclude 等機制,避免重複撰寫相同的標頭、腳本或樣式。
  • 動態資料注入:在渲染階段把資料(如 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 %}

說明

  • extendshome.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/cssstatic/jsstatic/img
模板快取導致舊資料 Jinja2 會快取已編譯的模板,開發階段修改模板卻沒即時顯示。 在開發環境設定 templates.env.auto_reload = True,或在 FastAPI 啟動指令加上 --reload
POST 後直接回傳 HTML 使用者刷新頁面會再次送出表單,產生重複資料。 使用 Post/Redirect/Get:回傳 RedirectResponse(303 或 302)。
大量資料一次渲染 把上千筆資料一次塞進模板會拖慢回應時間。 採用分頁、懶加載或 AJAX 部分渲染。

最佳實踐

  1. 統一管理模板變數:使用 context_processors(自行封裝)在每次渲染時自動注入共用變數(如 site_namenow)。
  2. 分層目錄templates/layoutstemplates/pagestemplates/partials,讓檔案結構一目了然。
  3. 安全性:在模板中避免直接輸出使用者輸入,預設 Jinja2 會自動 HTML-escape,如需原樣輸出請使用 | safe,但務必確保資料已經過清理。
  4. 靜態資源版本化:在引用 CSS/JS 時加入 query string(例:style.css?v=20251120)或使用 StaticFileshtml=True 讓瀏覽器快取失效,避免部署後舊資源卡住。

實際應用場景

場景 為何適合使用 FastAPI 的模板 示例功能
企業內部管理系統 需要快速的 API 與同時提供管理介面,開發成本低。 員工列表、權限設定、報表產生。
行銷活動單頁 輕量級的 Landing Page,結合表單收集 Leads。 A/B 測試、即時統計、外部追蹤腳本。
文件產生器 依據資料庫內容產生 PDF/HTML 報告。 使用 WeasyPrint 把渲染好的 HTML 轉成 PDF。
IoT 裝置監控儀表板 大量即時資料可透過 API 取得,同時提供圖形化儀表板。 使用 Chart.js、WebSocket 搭配模板渲染初始頁面。
教育平台 課程內容、測驗題目等需要動態渲染,同時提供 JSON API 給前端 SPA。 影片列表、作業上傳、即時評分。

總結

FastAPI 不僅是高效能的 JSON API 框架,也能透過 statictemplates 兩大功能,輕鬆產出 HTML 回應。本文從設定、範例、常見陷阱到實務場景,提供了完整的概念與操作步驟,讓你在開發 動態網站表單應用混合式前後端 時,能夠:

  • 正確掛載靜態資源與模板目錄
  • 使用 TemplateResponse 搭配 request 產生安全的 HTML
  • 利用 layout / block 讓版型可重用、維護成本降低
  • 結合 AJAX、POST/Redirect/Get 等技術,提升使用者體驗
  • 避免常見錯誤,遵循最佳實踐,確保專案長期可維護

掌握以上技巧後,你就能在 FastAPI 生態系中,快速構建出既具 API 能力又有完整前端呈現的全端應用。祝開發順利,期待看到你用 FastAPI 打造的精彩作品!