本文 AI 產出,尚未審核

FastAPI 應用組織:app.include_router() 的使用與最佳實踐


簡介

在開發中大型的 RESTful API 時,維持程式碼的可讀性與可維護性 是最重要的課題之一。FastAPI 本身提供了「路由 (router)」的概念,讓我們可以把不同功能模組化、分層管理,而不必把所有端點全部寫在 main.py 中。

app.include_router() 正是 FastAPI 用來 將子路由器 (router) 注入主應用 的核心方法。透過它,我們可以把 CRUD、驗證、管理介面等功能各自放在獨立的檔案或套件,讓專案結構更清晰、測試更方便,也能在日後需求變更時快速定位與調整。

本文將從概念說明、實作範例、常見陷阱到實務應用,完整介紹 app.include_router() 的使用方式,幫助初學者快速上手,同時提供中階開發者優化專案結構的參考。


核心概念

1. 為何要使用 Router

  • 模組化:把相關的 API 放在同一個 Router 中,例如 usersitemsauth
  • 重用性:同一個 Router 可以在多個 FastAPI 應用或測試客戶端中重複使用。
  • 前綴與標籤:在 include_router 時一次設定路徑前綴 (prefix) 與 OpenAPI 標籤 (tags),減少重複程式碼。

2. 建立子 Router

FastAPI 使用 APIRouter 類別來建立子路由器。最簡單的寫法如下:

# routers/users.py
from fastapi import APIRouter

router = APIRouter()

@router.get("/", summary="取得使用者列表")
async def list_users():
    return [{"id": 1, "name": "Alice"}]

@router.get("/{user_id}", summary="取得單一使用者")
async def get_user(user_id: int):
    return {"id": user_id, "name": "Bob"}
  • router = APIRouter() 產生一個路由器實例。
  • 之後在 main.py 中使用 app.include_router(router, prefix="/users", tags=["使用者"]) 把它掛上主應用。

3. app.include_router() 的參數

參數 說明 範例
router 必填,APIRouter 物件 router
prefix 路由前綴,所有端點會自動加上此字串 "/users"
tags OpenAPI 文件的分組標籤,支援多個 ["使用者"]
dependencies 全局依賴項,會套用到該 Router 所有路由 [Depends(get_current_user)]
responses 自訂全局回應描述 {404: {"description": "找不到資源"}}
include_in_schema 是否在文件中顯示,預設 True False

4. 多層 Router 的組合

在大型專案中,可能會有「功能模組」與「子功能」的層級。例如:admin 模組下又分 usersreports。此時可以先在 admin 包中建立一個 Router,然後再把子 Router 包含進去:

# routers/admin/users.py
from fastapi import APIRouter

router = APIRouter()

@router.get("/", tags=["管理員-使用者"])
async def admin_list_users():
    return [{"id": 1, "name": "Admin Alice"}]
# routers/admin/__init__.py
from fastapi import APIRouter
from . import users, reports

router = APIRouter(prefix="/admin", tags=["管理員"])

router.include_router(users.router)      # 直接把子 router 加入
router.include_router(reports.router)    # 同理

最後在 main.py 只需要一次 app.include_router(admin.router),即可一次掛上所有子路由。

5. 範例:完整專案結構

project/
│
├─ app/
│   ├─ main.py
│   ├─ routers/
│   │   ├─ __init__.py
│   │   ├─ users.py
│   │   ├─ items.py
│   │   └─ auth.py
│   └─ dependencies.py
│
└─ tests/
    └─ test_users.py

app/main.py

from fastapi import FastAPI
from .routers import users, items, auth

app = FastAPI(title="範例 API", version="1.0.0")

# 把各功能路由掛上主應用
app.include_router(users.router, prefix="/users", tags=["使用者"])
app.include_router(items.router, prefix="/items", tags=["商品"])
app.include_router(auth.router, prefix="/auth", tags=["認證"])

app/routers/items.py

from fastapi import APIRouter, HTTPException

router = APIRouter()

# 假資料庫
FAKE_DB = {
    1: {"name": "蘋果", "price": 30},
    2: {"name": "香蕉", "price": 10},
}

@router.get("/", summary="取得所有商品")
async def list_items():
    return list(FAKE_DB.values())

@router.get("/{item_id}", summary="取得單一商品")
async def get_item(item_id: int):
    item = FAKE_DB.get(item_id)
    if not item:
        raise HTTPException(status_code=404, detail="商品不存在")
    return item

app/routers/auth.py(示範全域依賴)

from fastapi import APIRouter, Depends, HTTPException, status

router = APIRouter()

def verify_token(token: str = None):
    if token != "secrettoken":
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
                            detail="未授權")
    return True

@router.get("/protected", dependencies=[Depends(verify_token)],
            summary="受保護的資源")
async def protected_endpoint():
    return {"msg": "你已通過驗證"}

以上範例展示了 路由的分層、前綴、標籤與依賴 的完整寫法。


常見陷阱與最佳實踐

陷阱 描述 解決方式
重複前綴 APIRouter 中已設定 prefix,再於 include_router 加一次,會導致路徑變成 //xxx 或重複。 只在一處設定,通常在 include_router 時設定前綴;若在子 Router 已經寫好前綴,則 include_router 時不要再加。
依賴項寫在錯誤層級 把全域依賴寫在子 Router,而不是在 include_router 時傳入,會導致其他路由意外套用。 依賴的作用範圍要明確:路由層級router.dependencies應用層級app.include_router(..., dependencies=…)
未使用 tags OpenAPI 文件缺乏分組,導致 API 文檔難以閱讀。 include_router 時提供 tags,或在子 Router 中使用 @router.get(..., tags=[...])
測試時忘記掛載 Router 單元測試只建立 TestClient(app),但未 include_router,測試會 404。 測試前 確保載入完整的 main 應用,或在測試模組中手動 app.include_router(...)
路徑衝突 兩個 Router 設定相同的路徑與方法,FastAPI 會以最後一次 include_router 為主,造成意外覆寫。 檢查路徑唯一性,或使用不同的 prefixname 參數避免衝突。

最佳實踐

  1. 每個功能一個檔案users.pyitems.pyauth.py,保持單一職責。
  2. include_router 時一次設定 prefixtagsdependencies,讓子 Router 只負責路由本身。
  3. 使用 name 參數為每個路由命名,方便在 url_for 產生 URL。
@router.get("/{user_id}", name="取得使用者")
async def get_user(user_id: int):
    ...
# 在其他地方
url = app.url_path_for("取得使用者", user_id=5)
  1. 將共用依賴、例外處理、CORS 設定放在 main.py,避免在子 Router 重複。
  2. 撰寫路由測試:使用 TestClient 直接呼叫子 Router,確保每個模組都能獨立運作。

實際應用場景

1. 電商平台的 API

  • 使用者相關/users/*(註冊、登入、個人資訊)
  • 商品相關/products/*(列表、細節、搜尋)
  • 訂單相關/orders/*(建立、查詢、付款)

每個領域都有自己的資料庫模型與業務邏輯,透過 app.include_router() 把它們分別掛載,開發團隊可以同時在不同子模組上平行開發,且不會互相干擾。

2. 內部管理系統(Admin)

管理系統往往需要 權限驗證操作日誌 等全域功能。可以先在 admin/__init__.py 建立一個 admin_router,在 include_router 時加入 dependencies=[Depends(admin_auth)],所有子路由自動套用驗證與日誌。

app.include_router(admin.router, prefix="/admin", tags=["管理介面"],
                  dependencies=[Depends(admin_auth)])

3. 微服務與 API Gateway

在微服務架構下,單一服務只負責少數幾個領域。透過 include_router,每個服務的 main.py 只需要掛載自己負責的 Router,Gateway 再以 APIRouter 方式聚合多個服務的路由,形成統一入口。


總結

app.include_router() 是 FastAPI 組織大型 API 的關鍵工具。透過 路由模組化、前綴與標籤統一設定、全域依賴注入,我們可以:

  • 提升程式碼可讀性:每個功能都有專屬檔案與路由。
  • 加速開發與測試:子 Router 可獨立測試,且不必每次都載入整個應用。
  • 降低衝突與維護成本:路徑、依賴與文件分組清晰,避免意外覆寫。

在實務專案中,建議從一開始就規劃好 router 的層級與命名規則,並遵守上述的最佳實踐。如此一來,隨著功能的持續擴充,FastAPI 應用仍能保持結構清晰、效能穩定,讓開發團隊專注於業務邏輯本身,而不是糾結於路由的混亂。

祝你在 FastAPI 的開發旅程中,寫出乾淨、易維護的 API! 🎉