FastAPI 應用組織:app.include_router() 的使用與最佳實踐
簡介
在開發中大型的 RESTful API 時,維持程式碼的可讀性與可維護性 是最重要的課題之一。FastAPI 本身提供了「路由 (router)」的概念,讓我們可以把不同功能模組化、分層管理,而不必把所有端點全部寫在 main.py 中。
app.include_router() 正是 FastAPI 用來 將子路由器 (router) 注入主應用 的核心方法。透過它,我們可以把 CRUD、驗證、管理介面等功能各自放在獨立的檔案或套件,讓專案結構更清晰、測試更方便,也能在日後需求變更時快速定位與調整。
本文將從概念說明、實作範例、常見陷阱到實務應用,完整介紹 app.include_router() 的使用方式,幫助初學者快速上手,同時提供中階開發者優化專案結構的參考。
核心概念
1. 為何要使用 Router
- 模組化:把相關的 API 放在同一個 Router 中,例如
users、items、auth。 - 重用性:同一個 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 模組下又分 users、reports。此時可以先在 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 為主,造成意外覆寫。 |
檢查路徑唯一性,或使用不同的 prefix、name 參數避免衝突。 |
最佳實踐
- 每個功能一個檔案:
users.py、items.py、auth.py,保持單一職責。 - 在
include_router時一次設定prefix、tags、dependencies,讓子 Router 只負責路由本身。 - 使用
name參數為每個路由命名,方便在url_for產生 URL。
@router.get("/{user_id}", name="取得使用者")
async def get_user(user_id: int):
...
# 在其他地方
url = app.url_path_for("取得使用者", user_id=5)
- 將共用依賴、例外處理、CORS 設定放在
main.py,避免在子 Router 重複。 - 撰寫路由測試:使用
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! 🎉