一個 HTTP 請求進來,誰負責什麼

你的後端每天處理幾千幾萬個 HTTP 請求。每個請求進來,需要有人做這些事:

  1. 解析 URL,決定「這個請求要去哪個 handler」
  2. 在 handler 執行之前,做一堆前置動作(驗身份、解析 body、記 log)
  3. 執行 handler,呼叫商業邏輯
  4. 格式化 response,送回去
  5. 如果出錯,攔截例外、回傳統一格式的錯誤訊息

Framework 的核心職責就是把這個流程結構化。 讓你不用每個請求都從零開始寫這些機制,讓新人看到程式碼就知道每個環節在哪裡。


Framework 實際做的四件事

1. 路由(Routing)

路由解決一個最基本的問題:「GET /users/42 要交給哪個函式處理?」

// Express:手動宣告
app.get('/users/:id', UserController.show);
 
// NestJS:decorator
@Get(':id')
show(@Param('id') id: string) { ... }
 
// Spring Boot:annotation
@GetMapping("/users/{id}")
public User show(@PathVariable Long id) { ... }

三種框架的語法不同,但做的事一樣:把 URL pattern 對應到 handler function。背後的實作通常是 trie 或 radix tree,讓路由查詢的時間複雜度接近 O(1)。

路由還負責:

  • 動態參數(:id{id})的提取
  • HTTP method 的匹配(GET vs POST)
  • Route group 和 prefix(/api/v1/

2. 中介層(Middleware)

Middleware 是一個可以在 handler 執行前後插入邏輯的機制。Request 進來後,會先通過一條 middleware 鏈,再到達 handler:

Request → [log] → [auth] → [validate] → Handler → [format] → Response

每個 middleware 可以:

  • 讀取或修改 request(例如解析 JWT、把 req.user 掛上去)
  • 提早回傳 response(例如 auth 失敗就直接回 401)
  • 把執行權傳給下一個 middleware(Express 的 next()、Koa 的 await next()

不同框架的 middleware 模型有微妙差異——這個主題在 Middleware 模型比較 詳細展開。

3. 依賴注入(Dependency Injection)

這是框架差異最大的地方。

有 DI 容器的框架(NestJS、Spring Boot、ASP.NET Core):你宣告「這個 Service 需要那個 Repository」,框架幫你建立實例、管理生命週期、注入依賴。你不需要手動 new 任何東西。

// NestJS:框架管理依賴
@Injectable()
class UserService {
  constructor(private userRepo: UserRepository) {} // 框架注入
}

沒有 DI 容器的框架(Express、Gin、Fastify):你自己決定怎麼建立依賴、怎麼傳遞。常見做法是手動建立 singleton、或用 closure 把依賴帶進去。

// Express:手動管理
const userRepo = new UserRepository(db);
const userService = new UserService(userRepo);
const userController = new UserController(userService);

沒有 DI 不代表不好,但代表你要自己設計依賴管理的方式。DI 的概念在 依賴注入 完整解釋。

4. 生命週期管理(Lifecycle)

Production backend 需要正確地「啟動」和「關閉」:

  • 啟動:middleware 先掛好、routes 先登記、DB 連線確認後再開始接請求
  • 關閉:收到 SIGTERM 後,不再接新請求、等 in-flight request 處理完、依序關閉 DB pool / Redis / Queue

不同框架對啟動和關閉的控制程度不一樣:

Framework啟動 hook關閉 hook
Express無內建(自己處理 process.on('SIGTERM')無內建
NestJSonModuleInit(), onApplicationBootstrap()onModuleDestroy(), beforeApplicationShutdown()
Spring BootApplicationContext 啟動、@PostConstruct@PreDestroy, DisposableBean
FastAPI@app.on_event('startup'), lifespan context@app.on_event('shutdown')

Express 什麼都不給,你要自己寫 process.on('SIGTERM', gracefulShutdown)。Spring Boot 有完整的應用程式生命週期管理。兩者不是好壞,是你願意管多少事的選擇。


Framework 不做的事

明確一下範圍,避免把框架搞成萬能工具箱:

  • 不負責資料庫操作:ORM(Sequelize / TypeORM / JPA / Eloquent)是另一層,框架只是讓你更容易整合
  • 不負責商業邏輯:controller 呼叫 service,service 做邏輯——這部分框架沒有意見
  • 不負責部署:Docker / K8s / PM2 是部署層,框架只管 HTTP 處理

為什麼「知道 framework 做了什麼」很重要

換框架的時候,你才知道哪些東西要自己補。

從 Express 換 NestJS:你得學 Module / Injectable / Guard,因為 NestJS 把 DI 和生命週期管理都做進去了,你之前自己手寫的那一層要換成框架的方式。

從 Django 換 FastAPI:你得學 Depends(),因為 FastAPI 的 DI 模型跟 Django 完全不同。

從 Spring Boot 換 Ktor:你得接受很多 Spring 幫你管的事(AOP / Bean lifecycle)現在要自己寫,或找對應的 Ktor plugin。


接下來

了解 framework 做什麼之後,下一個問題是:為什麼不直接用 stdlib 就好。這個問題的答案讓你真正感受到 framework 的邊界在哪裡。