本文 AI 產出,尚未審核

FastAPI 路由(Routing)

路由群組化與模組化


簡介

FastAPI 中,路由(endpoint)是與前端或其他服務溝通的入口。隨著專案規模逐漸增長,單一 main.py 檔案裡塞滿所有路由會讓程式碼變得難以維護、測試與重構。
透過 路由群組化(Router Grouping)模組化(Modularization),我們可以把相關的 API 按功能切分成不同的子模組,讓程式結構更清晰、開發流程更協作。

本篇文章將說明:

  1. 為什麼需要將路由切割成模組
  2. 如何使用 APIRouter 進行路由群組化
  3. 常見的陷阱與最佳實踐
  4. 在實務專案中如何應用這套技巧

即使你是 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 會把子路由的所有路徑合併到主應用,且保留原本的 tagsdependenciesresponses 等宣告。


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. 多層路由與子路由

大型系統常會有「模組」→「子模組」的層級,例如 adminuser 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 的 importlibpkgutil,我們可以自動掃描 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/ 等獨立層。

最佳實踐

  1. 功能分層:一個子模組只負責一個領域(如 auth, products, orders)。
  2. 統一前綴與標籤:讓 Swagger UI 能一眼看出 API 的結構。
  3. 依賴集中管理:使用 dependencies 參數或自訂 Depends 函式,避免重複程式碼。
  4. 單元測試:每個 router 都寫至少一個測試,確保路由變更不會破壞既有功能。
  5. 文件化:在每個子模組的 __init__.py 中加入簡短說明,讓新加入的開發者快速了解模組目的。

實際應用場景

1. 電子商務平台

  • 模組auth, products, orders, admin
  • 路由結構
/auth/login
/auth/register
/products/
/products/{id}
/orders/
/admin/products/
/admin/users/

透過 APIRouteradmin 子模組可以在同一個檔案夾內再細分 productsusers,每個子路由只需要關心自己的業務邏輯。

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 配合 prefixtagsdependencies,即可快速打造分層、可重用的路由。
  • 注意路徑衝突、依賴重複與循環匯入等常見陷阱,並遵守 單一職責統一文件測試覆蓋 的最佳實踐。
  • 在電商、SaaS、多租戶或微服務環境中,路由模組化更是不可或缺的基礎設計。

透過本文的範例與原則,你現在可以把原本雜亂的 main.py 轉變成 乾淨、可擴充 的 FastAPI 專案結構,為未來的功能迭代與團隊開發奠定堅實基礎。祝開發順利! 🚀