FastAPI 路由(Routing)
路由群組化與模組化
簡介
在 FastAPI 中,路由(endpoint)是與前端或其他服務溝通的入口。隨著專案規模逐漸增長,單一 main.py 檔案裡塞滿所有路由會讓程式碼變得難以維護、測試與重構。
透過 路由群組化(Router Grouping) 與 模組化(Modularization),我們可以把相關的 API 按功能切分成不同的子模組,讓程式結構更清晰、開發流程更協作。
本篇文章將說明:
- 為什麼需要將路由切割成模組
- 如何使用
APIRouter進行路由群組化 - 常見的陷阱與最佳實踐
- 在實務專案中如何應用這套技巧
即使你是 FastAPI 的新手,只要跟著範例走,也能快速建立可擴充的 API 結構。
核心概念
1. APIRouter 基本用法
APIRouter 是 FastAPI 提供的路由容器,功能類似於 Flask 的 Blueprint。它允許我們先在子模組裡定義路由,最後在主程式中統一掛載。
# routers/users.py
from fastapi import APIRouter
router = APIRouter() # 建立一個 Router 實例
@router.get("/users", tags=["User"])
async def list_users():
"""
取得所有使用者資料
"""
return [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]
在 main.py 中只需要把 router 加入應用即可:
# main.py
from fastapi import FastAPI
from routers import users # 直接匯入子模組的 router
app = FastAPI()
app.include_router(users.router) # 掛載子路由
重點:
include_router會把子路由的所有路徑合併到主應用,且保留原本的 tags、dependencies、responses 等宣告。
2. 路由前綴(Prefix)與標籤(Tags)
為了讓 API 文件更有層次,我們常會給子路由加上前綴與統一的標籤:
# routers/items.py
from fastapi import APIRouter
router = APIRouter(
prefix="/items", # 所有路徑會自動加上 /items 前綴
tags=["Item"], # Swagger UI 中會分組顯示
)
@router.get("/", summary="取得所有商品")
async def read_items():
return [{"id": 1, "name": "Laptop"}]
@router.post("/", summary="新增商品")
async def create_item(item: dict):
# 假設寫入資料庫...
return {"msg": "商品已建立", "item": item}
主程式只需要一次掛載:
# main.py
from fastapi import FastAPI
from routers import items, users
app = FastAPI()
app.include_router(users.router) # 無前綴
app.include_router(items.router) # 前綴已在子模組內設定
3. 多層路由與子路由
大型系統常會有「模組」→「子模組」的層級,例如 admin → user management。我們可以在子模組裡再建立 APIRouter,然後在父模組中一次性掛載。
# routers/admin/users.py
from fastapi import APIRouter
router = APIRouter(
prefix="/admin/users",
tags=["Admin User"],
)
@router.delete("/{user_id}", summary="刪除使用者(管理員專用)")
async def delete_user(user_id: int):
# 刪除邏輯...
return {"msg": f"使用者 {user_id} 已被刪除"}
再在 routers/admin/__init__.py 中匯出:
# routers/admin/__init__.py
from . import users
router = users.router # 直接把子 router 暴露出去
最後在 main.py:
# main.py
from fastapi import FastAPI
from routers import admin, users, items
app = FastAPI()
app.include_router(users.router)
app.include_router(items.router)
app.include_router(admin.router) # 包含 admin/users 路由
4. 路由依賴(Dependencies)統一管理
如果整個子模組都需要相同的驗證或資料庫連線,我們可以在建立 APIRouter 時一次宣告 dependencies:
# routers/secure.py
from fastapi import APIRouter, Depends, HTTPException, status
def get_current_user(token: str = Depends(...)):
# 假設驗證 token...
if token != "valid-token":
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
return {"username": "alice"}
router = APIRouter(
prefix="/secure",
tags=["Secure"],
dependencies=[Depends(get_current_user)], # 所有路由自動套用
)
@router.get("/profile")
async def profile(user: dict = Depends(get_current_user)):
return {"msg": "這是受保護的個人資訊", "user": user}
這樣所有 /secure/* 的路徑都會先經過 get_current_user 驗證,減少重複寫 Depends 的程式碼。
5. 動態載入路由(自動化模組掃描)
在大型專案中,手動 app.include_router(...) 會變得繁瑣。利用 Python 的 importlib 與 pkgutil,我們可以自動掃描 routers 資料夾下的所有子模組,並自動掛載。
# utils/router_loader.py
import importlib
import pkgutil
from fastapi import FastAPI
def auto_include_routers(app: FastAPI, package_name: str = "routers"):
"""
自動尋找 package_name 內的所有 router 並掛載
"""
package = importlib.import_module(package_name)
for _, module_name, is_pkg in pkgutil.iter_modules(package.__path__):
full_name = f"{package_name}.{module_name}"
module = importlib.import_module(full_name)
if hasattr(module, "router"):
app.include_router(module.router)
# main.py
from fastapi import FastAPI
from utils.router_loader import auto_include_routers
app = FastAPI()
auto_include_routers(app) # 只要 routers 資料夾內有 router,就會自動掛載
實務建議:在正式環境仍建議保留明確的
include_router呼叫,方便閱讀與除錯;自動載入適合在原型或快速開發階段使用。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
| 路徑衝突 | 不同子模組使用相同的路徑(如 /items)會造成覆寫。 |
為每個子模組設定 prefix,或在 include_router 時使用 prefix 參數覆寫。 |
| 依賴重複宣告 | 同一依賴在子路由與全局路由都寫,導致執行兩次。 | 盡量把共用依賴放在 APIRouter 建構階段的 dependencies,避免在每個路由內再次宣告。 |
| Swagger 標籤混亂 | 標籤(tags)未統一,文件會散亂。 | 在子模組建立 APIRouter 時就指定 tags,或在 include_router 時傳入 tags 參數。 |
| 測試困難 | 子模組的 router 沒有單獨測試入口。 | 為每個 router 建立 TestClient 測試檔,確保模組化後仍能獨立測試。 |
| 循環匯入 | 子模組相互引用 app 或其他 router,產生 ImportError。 |
使用 依賴注入(Depends)或將共用程式碼抽到 services/、models/ 等獨立層。 |
最佳實踐:
- 功能分層:一個子模組只負責一個領域(如
auth,products,orders)。 - 統一前綴與標籤:讓 Swagger UI 能一眼看出 API 的結構。
- 依賴集中管理:使用
dependencies參數或自訂Depends函式,避免重複程式碼。 - 單元測試:每個 router 都寫至少一個測試,確保路由變更不會破壞既有功能。
- 文件化:在每個子模組的
__init__.py中加入簡短說明,讓新加入的開發者快速了解模組目的。
實際應用場景
1. 電子商務平台
- 模組:
auth,products,orders,admin - 路由結構:
/auth/login
/auth/register
/products/
/products/{id}
/orders/
/admin/products/
/admin/users/
透過 APIRouter,admin 子模組可以在同一個檔案夾內再細分 products、users,每個子路由只需要關心自己的業務邏輯。
2. 多租戶 SaaS 系統
- 每個租戶都有自己的 API 前綴(如
/tenant/{tenant_id}/...)。 - 在
tenant_router = APIRouter(prefix="/tenant/{tenant_id}")中加入 依賴,驗證租戶權限後再進一步呼叫子路由。
tenant_router = APIRouter(
prefix="/tenant/{tenant_id}",
dependencies=[Depends(validate_tenant)],
)
tenant_router.include_router(products.router) # 產品相關路由
tenant_router.include_router(orders.router) # 訂單相關路由
app.include_router(tenant_router)
這樣的設計讓 多租戶 的驗證與資源隔離僅寫一次,所有子路由自動受惠。
3. 微服務聚合閘道(API Gateway)
- 主應用僅作為 聚合層,把多個子服務的路由分別放在不同的子模組,並使用 依賴 轉發請求。
- 每個子模組只需要宣告遠端服務的 URL、請求方法與錯誤處理,主程式不必關心細節。
# routers/user_service.py
from fastapi import APIRouter, HTTPException
import httpx
router = APIRouter(prefix="/users")
@router.get("/{user_id}")
async def get_user(user_id: int):
async with httpx.AsyncClient() as client:
resp = await client.get(f"http://user-service/api/users/{user_id}")
if resp.status_code != 200:
raise HTTPException(status_code=resp.status_code, detail=resp.text)
return resp.json()
總結
- 路由群組化 讓 API 結構更清晰,模組化 則提升可維護性與團隊協作效率。
- 使用
APIRouter配合 prefix、tags、dependencies,即可快速打造分層、可重用的路由。 - 注意路徑衝突、依賴重複與循環匯入等常見陷阱,並遵守 單一職責、統一文件、測試覆蓋 的最佳實踐。
- 在電商、SaaS、多租戶或微服務環境中,路由模組化更是不可或缺的基礎設計。
透過本文的範例與原則,你現在可以把原本雜亂的 main.py 轉變成 乾淨、可擴充 的 FastAPI 專案結構,為未來的功能迭代與團隊開發奠定堅實基礎。祝開發順利! 🚀