Express & Fastify
規則
- 按領域組織路由:
routes/users.ts、routes/orders.ts — 每個資源一個路由器
- 使用 Zod 驗證所有輸入:在處理前解析
req.body、req.params、req.query
- 集中式錯誤處理:全域攔截中介軟體 (Express) 或
setErrorHandler (Fastify)
- 回傳統一的錯誤格式:
{ error: { code: string, message: string, details?: unknown } }
- TypeScript:使用泛型為請求/回應指定型別 —
Request<Params, ResBody, ReqBody, Query>
- 中介軟體順序:CORS、body 解析器、認證、速率限制、路由、錯誤處理
- 使用非同步路由處理器 — 搭配錯誤捕獲或使用
express-async-errors
- Fastify:使用插件系統封裝路由和裝飾器 —
fastify.register()
- Fastify 結構描述:定義 JSON Schema 或使用
@fastify/type-provider-zod 進行驗證和序列化
- 健康檢查:
GET /health — 回傳 200 並包含資料庫/Redis 連線狀態
- 優雅關機:監聽 SIGTERM,停止接受連線,完成進行中的請求
模式
import { Router } from "express";
import { z } from "zod";
const router = Router();
const CreateUserSchema = z.object({ email: z.string().email(), name: z.string().min(1) });
router.post("/users", async (req, res, next) => {
try {
const body = CreateUserSchema.parse(req.body);
const user = await createUser(body);
res.status(201).json(user);
} catch (err) { next(err); }
});
import Fastify from "fastify";
import { serializerCompiler, validatorCompiler, ZodTypeProvider } from "fastify-type-provider-zod";
const app = Fastify().withTypeProvider<ZodTypeProvider>();
app.setValidatorCompiler(validatorCompiler);
app.setSerializerCompiler(serializerCompiler);
app.post("/users", { schema: { body: CreateUserSchema } }, async (req) => {
return createUser(req.body);
});
應避免
- 將業務邏輯放在路由處理程式中 — 應提取到服務/領域層
- 忘記 Express 的非同步錯誤處理 — 未處理的拒絕會導致程序崩潰
- 在一個檔案中匯入所有路由 — 應使用
router.use() 或 Fastify 插件以實現模組化
- 因為「前端會驗證」而跳過輸入驗證 — 永遠在伺服器端進行驗證