本文 AI 產出,尚未審核

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

主題:Mount 靜態資料夾


簡介

在 Web 應用程式中,靜態資源(如 CSS、JavaScript、圖片、字型檔)是不可或缺的組成部分。若沒有妥善提供這些檔案,前端畫面就無法正確呈現,使用者體驗也會大打折扣。FastAPI 雖以 API 為主,但同樣支援 Mount 靜態資料夾的功能,讓你可以在同一個服務中同時提供 API 與前端靜態資源。

本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,逐步帶領讀者了解 如何在 FastAPI 中掛載(mount)靜態資料夾,並示範在實務專案裡的應用方式。即使你是剛接觸 FastAPI 的新手,也能在閱讀完本篇後,快速在自己的專案中加入靜態檔案服務。


核心概念

1. 為什麼要「Mount」靜態資料夾?

  • 分離職責:API 路由與前端資源分開管理,讓程式碼結構更清晰。
  • 效能優化:FastAPI 內建的 StaticFiles 會使用 aiofiles 非同步讀取檔案,對於大量的靜態請求仍能保持高效能。
  • 路徑管理:透過 app.mount(),可以在同一個 FastAPI 實例下,為不同的 URL 前綴(prefix)掛載不同的資料夾,彈性極高。

2. StaticFilesMount 的基本使用

FastAPI 直接使用 StarletteStaticFiles 類別來提供靜態檔案服務。最簡單的掛載方式如下:

from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles

app = FastAPI()

# 把 ./static 目錄掛載到 /static 路徑
app.mount("/static", StaticFiles(directory="static"), name="static")
  • "/static":客戶端請求的 URL 前綴。
  • StaticFiles(directory="static"):實際儲存靜態檔案的本機路徑。
  • name="static":給這個掛載點一個名稱,方便在 url_for 中引用(若使用 Jinja2 模板)。

3. 搭配 Jinja2 模板渲染靜態資源

在使用模板(templates)時,我們常需要在 HTML 中引用 CSS/JS。透過 url_for 可以取得正確的靜態路徑:

from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates

app = FastAPI()
templates = Jinja2Templates(directory="templates")

@app.get("/")
async def home(request: Request):
    # 在模板中使用 {{ url_for('static', path='css/main.css') }}
    return templates.TemplateResponse("index.html", {"request": request})

url_for('static', path='css/main.css') 會產生 /static/css/main.css,即使未來改變掛載路徑,只要修改 app.mount,模板內的引用會自動跟著更新。

4. 多個靜態資料夾的掛載

大型專案常會把 公共資源第三方套件使用者上傳檔案 分別放在不同資料夾。FastAPI 允許同時掛載多個靜態目錄:

# 公共資源
app.mount("/assets", StaticFiles(directory="public/assets"), name="assets")
# 第三方套件
app.mount("/vendor", StaticFiles(directory="node_modules"), name="vendor")
# 使用者上傳的檔案(只讀)
app.mount("/uploads", StaticFiles(directory="data/uploads", html=True), name="uploads")
  • html=True 讓目錄索引頁面(類似 index.html)可以直接被瀏覽。

5. 完整範例:從專案結構到程式碼

以下是一個典型的 FastAPI 專案結構,示範如何掛載靜態資料夾並使用模板:

my_fastapi_app/
├─ app/
│   ├─ main.py
│   ├─ routers/
│   │   └─ items.py
│   ├─ static/
│   │   ├─ css/
│   │   │   └─ style.css
│   │   └─ js/
│   │       └─ script.js
│   └─ templates/
│       └─ base.html
├─ requirements.txt
└─ uvicorn_start.sh

main.py

# main.py
from fastapi import FastAPI, Request
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from .routers import items

app = FastAPI(title="Demo - Mount 靜態資料夾")

# 1️⃣ 掛載 static 資料夾到 /static
app.mount("/static", StaticFiles(directory="app/static"), name="static")

# 2️⃣ 設定模板目錄
templates = Jinja2Templates(directory="app/templates")

# 3️⃣ 引入子路由
app.include_router(items.router, prefix="/api")

# 4️⃣ 渲染首頁,示範模板中引用 static
@app.get("/", name="home")
async def home(request: Request):
    return templates.TemplateResponse(
        "base.html",
        {"request": request, "title": "FastAPI 靜態檔案示範"}
    )

templates/base.html(使用 Jinja2):

<!DOCTYPE html>
<html lang="zh-Hant">
<head>
    <meta charset="UTF-8">
    <title>{{ title }}</title>
    <!-- 使用 url_for 產生正確的 CSS 路徑 -->
    <link rel="stylesheet" href="{{ url_for('static', path='css/style.css') }}">
</head>
<body>
    <h1>歡迎使用 FastAPI!</h1>
    <p>這是一個示範如何掛載靜態資料夾的範例。</p>

    <!-- 使用 url_for 產生正確的 JS 路徑 -->
    <script src="{{ url_for('static', path='js/script.js') }}"></script>
</body>
</html>

static/css/style.css

/* style.css */
body {
    background-color: #f9f9f9;
    font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
    margin: 2rem;
}
h1 {
    color: #2c3e50;
}

static/js/script.js

// script.js
console.log("FastAPI 靜態檔案已成功載入!");

啟動伺服器:

uvicorn app.main:app --reload

開啟瀏覽器 http://127.0.0.1:8000/,即可看到 CSS 與 JS 正常載入,且 API 路由仍可在 http://127.0.0.1:8000/api/items/ 使用。


常見陷阱與最佳實踐

陷阱 說明 解決方案
路徑錯誤 directory 指向錯誤的相對路徑,導致 404。 使用 絕對路徑Path(__file__).parent / "static")或在專案根目錄執行 uvicorn,確保相對路徑正確。
未加 name 參數 在模板中使用 url_for('static', ...) 會找不到對應的掛載點。 必須app.mount 加上 name(如 name="static")。
安全性問題 靜態目錄若放在不受限制的路徑,可能會被外部使用者直接讀取機密檔案。 限制目錄:只掛載公開資源;若需要提供上傳檔案,使用驗證或 Signed URL
Cache 控制 靜態檔案未設定快取,頻繁請求會浪費頻寬。 使用 StaticFilescache_control 參數或在 CDN 前端加上 Cache-Control 標頭。
跨域資源 (CORS) 前端若從不同子網域載入靜態檔案,可能遭到瀏覽器阻擋。 StaticFiles 前加上 CORSMiddleware,或在前端伺服器(NGINX)設定 Access-Control-Allow-Origin

最佳實踐

  1. 統一管理靜態路徑:在 settings.py 中定義 STATIC_URLSTATIC_ROOT,避免硬編碼。
  2. 分層掛載:將公共資源、第三方庫、使用者上傳分別掛載,降低相互影響的風險。
  3. 使用 CDN:在生產環境,將 StaticFiles 只做測試或備援,用 CDN 直接提供靜態檔案,可大幅提升效能。
  4. 開啟 gzip:在部署時(如使用 uvicorn + gunicorn)開啟壓縮,減少傳輸大小。
  5. 自動化測試:寫測試驗證 /static/* 是否正確返回 200,防止路徑變更後斷裂。

實際應用場景

場景 為什麼需要 Mount 靜態資料夾 實作要點
單頁應用(SPA) 前端框架(React、Vue、Svelte)編譯後的 index.htmlbundle.jsstyles.css 全部是靜態檔案。 把前端產出的 dist/ 目錄掛載到 /static,並在根路由返回 index.html
文件與說明頁 API 說明、使用手冊常以 HTML、PDF 形式提供。 建立 docs/ 目錄,掛載至 /docs,並設定 html=True 讓目錄索引可直接瀏覽。
使用者上傳的圖片 社群、部落格等平台需要提供圖片瀏覽。 把上傳目錄掛載到 /media,同時在路由層加入權限驗證(如 JWT)。
多租戶 SaaS 每個租戶有自訂的品牌資源(logo、CSS)。 在請求時動態決定掛載路徑或使用子路徑(/tenant1/static/tenant2/static)。
微服務整合 其他微服務只提供 API,前端資源由主服務統一提供。 主服務負責掛載所有前端資源,其餘服務只專注於業務 API。

總結

  • Mount 靜態資料夾 是 FastAPI 同時提供 API 與前端資源的關鍵功能,只要使用 app.mount("/prefix", StaticFiles(directory="path"), name="...") 即可輕鬆完成。
  • 搭配 Jinja2 或其他模板引擎時,透過 url_for('static', path='...') 可以自動產生正確的 URL,讓前端資源的路徑管理更彈性。
  • 在實務開發中,應注意路徑正確性、命名 (name)、安全性(避免暴露機密檔案)以及快取/跨域設定。
  • 多資料夾掛載、使用 CDN、加入 CORS 與壓縮等最佳實踐,能讓你的 FastAPI 應用在 效能安全可維護性 上都有更好的表現。

掌握了這些概念與技巧,你就能在 FastAPI 專案中,像搭建傳統網站一樣,輕鬆提供完整的前端靜態資源,同時保有 FastAPI 高效能的 API 能力。祝開發順利,期待你在實務中玩出更多創意!