本文 AI 產出,尚未審核

FastAPI – 模組化應用結構(Application Setup)

簡介

在開發 API 時,隨著功能逐漸增多,單一檔案的程式碼會變得難以維護、測試與部署。模組化的應用結構 能讓專案保持清晰的層次、方便團隊協作,且在部署到容器或伺服器時更易管理。FastAPI 本身支援 APIRouter、依賴注入(Dependency Injection)與 Pydantic 設定模型,這些特性正好配合模組化的設計理念。本文將一步步說明如何在 FastAPI 中建立一套易於擴充、易於測試的模組化專案。

核心概念

1. 為什麼需要模組化

  • 可讀性:每個功能區塊都有自己的目錄與檔案,開發者只需關注相關模組。
  • 可重用性:路由、服務、資料庫操作等可以在不同專案間搬移。
  • 測試便利:單元測試可以針對獨立模組撰寫,避免整體啟動成本。
  • 部署彈性:設定、環境變數與啟動腳本分離,讓 CI/CD 更簡潔。

2. 基本目錄結構

my_fastapi_app/
├─ app/
│  ├─ __init__.py
│  ├─ main.py                 # 入口點
│  ├─ api/
│  │  ├─ __init__.py
│  │  ├─ router.py           # 統一匯入子路由
│  │  ├─ users.py            # 使用者相關路由
│  │  └─ items.py            # 商品相關路由
│  ├─ core/
│  │  ├─ __init__.py
│  │  ├─ config.py           # 設定模型
│  │  └─ security.py         # 認證相關工具
│  ├─ db/
│  │  ├─ __init__.py
│  │  ├─ models.py           # ORM 模型
│  │  └─ session.py          # DB session
│  └─ services/
│     ├─ __init__.py
│     └─ user_service.py     # 業務邏輯
├─ tests/
│  ├─ __init__.py
│  └─ test_users.py
├─ .env                       # 環境變數
├─ requirements.txt
└─ alembic/                   # DB migration (optional)

重點app 目錄內依功能分層,api 放路由、core 放共用設定與工具、db 放資料庫相關、services 放業務邏輯。

3. APIRouter 的使用

# app/api/users.py
from fastapi import APIRouter, Depends, HTTPException, status
from ..services.user_service import UserService
from ..models import UserCreate, UserRead

router = APIRouter(prefix="/users", tags=["users"])

@router.post("/", response_model=UserRead, status_code=status.HTTP_201_CREATED)
async def create_user(payload: UserCreate, service: UserService = Depends()):
    """
    建立新使用者
    """
    return await service.create_user(payload)
# app/api/router.py
from fastapi import APIRouter
from . import users, items

api_router = APIRouter()
api_router.include_router(users.router)
api_router.include_router(items.router)
# app/main.py
from fastapi import FastAPI
from .api.router import api_router
from .core.config import settings

app = FastAPI(
    title=settings.PROJECT_NAME,
    version="0.1.0",
    description="示範 FastAPI 模組化結構"
)

app.include_router(api_router)

說明:每個子模組只負責自己的路由,router.py 統一把它們匯入 main.py,保持入口檔案乾淨。

4. 設定管理(Config)

# app/core/config.py
from pydantic import BaseSettings, Field

class Settings(BaseSettings):
    PROJECT_NAME: str = Field(..., env="PROJECT_NAME")
    DEBUG: bool = Field(False, env="DEBUG")
    DATABASE_URL: str = Field(..., env="DATABASE_URL")
    SECRET_KEY: str = Field(..., env="SECRET_KEY")

    class Config:
        env_file = ".env"
        env_file_encoding = "utf-8"

settings = Settings()

使用 settings 於程式任何需要的地方,不再硬編碼,且 .env 檔案可在不同環境切換。

5. 依賴注入(Dependency Injection)管理 DB Session

# app/db/session.py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, scoped_session
from ..core.config import settings

engine = create_engine(settings.DATABASE_URL, echo=settings.DEBUG)
SessionLocal = scoped_session(sessionmaker(autocommit=False, autoflush=False, bind=engine))

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

在服務層或路由中直接 Depends(get_db),保持資料庫連線的生命週期一致。

6. 業務服務層(Service Layer)

# app/services/user_service.py
from typing import List
from fastapi import Depends
from ..db.session import get_db
from sqlalchemy.orm import Session
from ..models import User, UserCreate, UserRead

class UserService:
    def __init__(self, db: Session = Depends(get_db)):
        self.db = db

    async def create_user(self, payload: UserCreate) -> UserRead:
        user = User(**payload.dict())
        self.db.add(user)
        self.db.commit()
        self.db.refresh(user)
        return UserRead.from_orm(user)

    async def list_users(self) -> List[UserRead]:
        users = self.db.query(User).all()
        return [UserRead.from_orm(u) for u in users]

好處:路由只負責請求/回應,所有資料庫操作與商業邏輯集中在 services,便於單元測試與未來重構。

常見陷阱與最佳實踐

陷阱 可能的結果 解決方式
把所有路由寫在 main.py 隨著功能增長,檔案變得龐大且難以閱讀 使用 APIRouter 分模組,並在 router.py 統一匯入
硬編碼設定值 部署到不同環境需要手動修改程式 Pydantic BaseSettings + .env,讓設定與程式碼分離
在路由內直接建立 DB 連線 每個請求都會重複建立連線,資源浪費 使用 依賴注入 (Depends(get_db)) 讓連線在請求結束時自動關閉
在服務層直接呼叫外部 API 測試時會觸發真實請求,難以斷言 把外部 API 包裝成 client class,在測試時使用 Mock
忽略例外處理 未捕捉的例外會讓服務直接 500 錯誤 使用 FastAPI 的 Exception Handler,統一回傳錯誤格式

最佳實踐小結

  1. 目錄分層:路由、設定、資料庫、服務分開管理。
  2. 依賴注入:所有外部資源(DB、Redis、外部 API)皆以 Depends 方式取得。
  3. 型別提示與 Pydantic:盡量使用 BaseModel 定義請求與回應,讓 IDE 與 FastAPI 自動產生文件。
  4. 測試友好:每個模組都能獨立 import,在 tests/ 中針對服務層與路由層寫測試。

實際應用場景

場景 需求 模組化帶來的好處
微服務 每個服務只提供特定領域的 API,如使用者、商品、訂單 只需在 docker-compose.yml 中掛載對應的子目錄,快速擴充或縮減功能
多租戶 SaaS 同一套程式碼支援多家客戶,設定不同資料庫 透過 settings 動態載入不同的 DATABASE_URL,服務層只需要 Depends(get_db) 即可
CI/CD 每次 PR 必須跑單元測試、整合測試 因為路由、服務與 DB 都是獨立模組,測試腳本只要 import app.main 即可啟動測試環境
版本升級 需要在不影響現有 API 的情況下加入新功能 新功能寫在新的 routerservice 中,舊的路由不變,且可以在 router.py 中設定 include_in_schema=False 暫時隱藏

總結

模組化的 FastAPI 應用結構不僅讓程式碼更乾淨、易於維護,也為團隊協作、測試自動化與多環境部署奠定基礎。透過 APIRouter依賴注入Pydantic 設定模型服務層 的分離,我們可以在專案成長的同時,保持高可讀性與高擴充性。從今天開始,依照本文的目錄規劃與最佳實踐,為你的 FastAPI 專案打造堅實的模組化基礎吧!