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. StaticFiles 與 Mount 的基本使用
FastAPI 直接使用 Starlette 的 StaticFiles 類別來提供靜態檔案服務。最簡單的掛載方式如下:
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 控制 | 靜態檔案未設定快取,頻繁請求會浪費頻寬。 | 使用 StaticFiles 的 cache_control 參數或在 CDN 前端加上 Cache-Control 標頭。 |
| 跨域資源 (CORS) | 前端若從不同子網域載入靜態檔案,可能遭到瀏覽器阻擋。 | 在 StaticFiles 前加上 CORSMiddleware,或在前端伺服器(NGINX)設定 Access-Control-Allow-Origin。 |
最佳實踐
- 統一管理靜態路徑:在
settings.py中定義STATIC_URL、STATIC_ROOT,避免硬編碼。 - 分層掛載:將公共資源、第三方庫、使用者上傳分別掛載,降低相互影響的風險。
- 使用 CDN:在生產環境,將
StaticFiles只做測試或備援,用 CDN 直接提供靜態檔案,可大幅提升效能。 - 開啟
gzip:在部署時(如使用uvicorn+gunicorn)開啟壓縮,減少傳輸大小。 - 自動化測試:寫測試驗證
/static/*是否正確返回 200,防止路徑變更後斷裂。
實際應用場景
| 場景 | 為什麼需要 Mount 靜態資料夾 | 實作要點 |
|---|---|---|
| 單頁應用(SPA) | 前端框架(React、Vue、Svelte)編譯後的 index.html、bundle.js、styles.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 能力。祝開發順利,期待你在實務中玩出更多創意!