兩個用 Python 寫的 API

版本 A:Django REST Framework

# models.py
class User(models.Model):
    name = models.CharField(max_length=100)
    email = models.EmailField(unique=True)
 
# serializers.py
class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'name', 'email']
 
# views.py
class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
 
# urls.py
router = DefaultRouter()
router.register(r'users', UserViewSet)

這四個檔案,你得到了完整的 CRUD API:GET /users/POST /users/GET /users/1/PUT /users/1/DELETE /users/1/,全部自動生成,還有 pagination、filter、search 可以加。

版本 B:FastAPI

from fastapi import FastAPI
from pydantic import BaseModel
 
app = FastAPI()
 
class User(BaseModel):
    name: str
    email: str
 
users_db: list[dict] = []
 
@app.get("/users")
def get_users():
    return users_db
 
@app.post("/users", status_code=201)
def create_user(user: User):
    users_db.append(user.dict())
    return user

這樣得到 GET /usersPOST /users,你要自己寫其他的。

兩個版本解決的是一樣的需求,但你能看見的邏輯量完全不同


Magic 是什麼

Magic 是「框架根據 convention 自動為你做了某些事,但你看不到它在哪裡做的」。

Django ModelViewSet 的 magic:

  • queryset = User.objects.all() → 框架自動把它接到 GET / endpoint
  • serializer_class = UserSerializer → 框架自動用它來序列化和反序列化
  • CRUD 的 URL routing 自動從 router.register 生成
  • pagination 預設就存在,除非你關掉

這些行為在 Django 的父類裡面。你繼承 ModelViewSet,得到所有行為,但你的程式碼裡看不到這些行為在哪裡被定義

Magic 的優點:開發速度極快。標準 CRUD 幾乎不用寫邏輯。

Magic 的代價:出了問題,debug 路徑是「去讀父類的原始碼」或「搜 Stack Overflow 那個神奇的行為是怎麼覆寫的」。自訂行為需要知道框架在哪個 hook 點讓你插入。


Explicit 是什麼

Explicit 是「每一個行為都在你的程式碼裡可見,你能追蹤執行路徑」。

Express 的 explicit:

// 你看得到每個 middleware 的掛載順序
app.use(morgan('dev'));
app.use(cors(corsConfig));
app.use(express.json());
 
// 你看得到每個 route 指向哪個 handler
app.get('/users', authenticate, UserController.index);
app.post('/users', authenticate, validate(userSchema), UserController.store);
 
// 你看得到 error 是怎麼被接住的
app.use((err, req, res, next) => {
  res.status(err.statusCode || 500).json({ error: err.message });
});

每一行都是你寫的,每一行都在說「這裡做了什麼」。

Explicit 的優點:出了問題,你直接在你的程式碼裡追蹤執行路徑。沒有隱藏行為,什麼都在你眼前。

Explicit 的代價:每個東西都要自己搭。你要設計 config 管理、要決定 error 處理策略、要選擇 DI 方式——這些在 opinionated 框架裡框架替你決定了。


光譜上的主要框架

Magic ←————————————————————————————————→ Explicit

Rails    Django    Spring Boot    FastAPI    NestJS    Express    Gin
Laravel  DRF       Laravel        Hono                 Fastify

這不是嚴格的排列,而是大致的位置感。

最 magic 的一端:Rails 和 Laravel。User.find(1) 就是查資料庫,has_many :posts 就是建立 association,你不需要設定任何東西,只要符合 convention。

中間地帶:Spring Boot 和 NestJS。有 DI container 管理依賴,有 decorator / annotation 定義行為,但比 Rails 更 explicit——你看得到 @Injectable()@Controller(),知道 Spring 在管理什麼。

最 explicit 的一端:Express 和 Gin。路由是你宣告的,middleware 是你掛的,error handler 是你寫的,DI 是你自己管的。框架只提供最薄的一層抽象。


什麼情況選哪邊

沒有絕對正確的答案,但有幾個可以幫助判斷的維度:

選 magic-heavy 框架(Rails / Django / Laravel)的情況

  • 需求標準:CRUD 為主,不太需要自訂 HTTP 行為
  • 團隊小、需要快速迭代
  • 後端不是核心競爭力,只是「讓前端有 API 可以用」

選 explicit 框架(Express / Gin / FastAPI)的情況

  • 需求特殊:streaming、WebSocket、特殊的 auth 流程、複雜的 middleware 邏輯
  • 團隊想要完全掌控架構決策
  • 需要精確控制效能瓶頸

選中間地帶(NestJS / Spring Boot)的情況

  • 大型系統:需要 DI container 讓依賴管理不失控
  • 多人開發:framework 的結構 convention 讓 onboarding 成本降低
  • 需要 opinionated 架構但不需要 magic routing

真正的問題不是選哪個

選框架最常見的錯誤是:用你熟悉的語言選,而不是依需求選。

用 Express 做一個大型 ERP 後端,你會花大量時間自己搭 DI、搭生命週期管理、搭統一錯誤處理——這些 NestJS 都已經做好了。

用 Django 做一個需要自訂 streaming response 或複雜 WebSocket 行為的系統,你會花大量時間對抗 Django 的 convention,最後發現 FastAPI 更適合。

問的問題應該是:這個系統的核心需求,和這個框架的設計哲學,吻合程度有多高?


延伸閱讀