後端開發
核心原則
- 無狀態服務 — 不要將工作階段狀態儲存在記憶體中;使用 Redis 或資料庫
- 快速失敗 — 在邊界驗證輸入,及早回傳錯誤
- 冪等性 — POST/PUT 端點必須能安全地重複呼叫
- 可觀察性 — 每個服務都需要日誌、指標和追蹤
API 端點結構
請求生命週期
請求 → 速率限制 → 驗證 → 授權 → 驗證 → 業務邏輯 → 回應
絕不跳過此順序。先驗證再進行業務邏輯。先驗證再處理。
回應標準
// 成功
{ "data": { ... }, "meta": { "page": 1, "total": 100 } }
// 錯誤
{
"error": {
"code": "VALIDATION_ERROR",
"message": "人類可讀的描述",
"details": [{ "field": "email", "issue": "格式無效" }]
}
}
驗證模式
JWT 最佳實踐
- 短暫的存取權杖 TTL(15 分鐘)+ 長時間的重新整理權杖(7 天)
- 將重新整理權杖儲存在 httpOnly cookie 中(不要使用 localStorage)
- 驗證簽名、過期時間、發行者與受眾
- 使用時輪換重新整理權杖
- 維護撤銷清單或僅使用短暫的權杖
基於工作階段的驗證
- 將工作階段 ID 儲存在 httpOnly、Secure、SameSite cookie 中
- 將工作階段資料儲存在 Redis 中並設定 TTL
- 登入後重新產生工作階段 ID(防止工作階段固定)
授權
// 在服務層檢查授權,而不僅是在路由層
async function getOrder(userId: string, orderId: string) {
const order = await db.orders.findById(orderId)
if (!order) throw new NotFoundError()
// 授權檢查 — 使用者只能看到自己的訂單
if (order.userId !== userId) throw new ForbiddenError()
return order
}
背景任務與佇列
使用佇列處理:
- 電子郵件發送
- 圖片/檔案處理
- 不需要同步進行的外部 API 呼叫
- 長時間執行的任務
任務模式
發送後忘記:入隊後立即返回
排程:在特定時間執行(cron)
延遲:在 X 分鐘/小時後執行
重試:失敗時以退避方式重新嘗試
務必實作:
- 死信佇列(處理失敗的任務)
- 冪等性金鑰(防止重複處理)
- 每個任務的超時設定
- 最大重試次數
錯誤處理
// 結構化錯誤層級
class AppError extends Error {
constructor(
public message: string,
public statusCode: number,
public code: string
) { super(message) }
}
class ValidationError extends AppError {
constructor(details: ValidationDetail[]) {
super('驗證失敗', 400, 'VALIDATION_ERROR')
this.details = details
}
}
// 全域錯誤處理器
app.use((err, req, res, next) => {
if (err instanceof AppError) {
return res.status(err.statusCode).json({ error: err })
}
// 記錄未預期的錯誤
logger.error('未預期的錯誤', { err, req })
res.status(500).json({ error: { code: 'INTERNAL_ERROR' } })
})
中介層檢查清單
每個生產環境 API 都需要:
- 請求記錄(方法、路徑、狀態、持續時間)
- 速率限制(依 IP、依使用者、依端點)
- CORS 設定(明確的白名單)
- Helmet / 安全性標頭
- 請求 ID 傳遞(用於分散式追蹤)
- 主體大小限制
- 超時處理
環境設定
// 絕不硬編碼設定 — 一律從環境變數取得
const config = {
db: {
url: process.env.DATABASE_URL, // 必要
poolSize: parseInt(process.env.DB_POOL_SIZE ?? '10')
},
jwt: {
secret: process.env.JWT_SECRET, // 必要
expiresIn: process.env.JWT_TTL ?? '15m'
}
}
// 啟動時驗證必要的設定 — 快速失敗
const required = ['DATABASE_URL', 'JWT_SECRET']
for (const key of required) {
if (!process.env[key]) throw new Error(`缺少必要的環境變數: ${key}`)
}