7.6 KiB
7.6 KiB
NestJS Template — 開發規範
技術棧
| 分類 | 套件 |
|---|---|
| Runtime | Node.js(ESM,"type": "module") |
| 框架 | NestJS 11 |
| 語言 | TypeScript 5(NodeNext 模組解析) |
| 套件管理 | pnpm |
| 資料庫 | PostgreSQL(@nestjs/typeorm + typeorm + pg) |
| 快取/佇列 | Redis(ioredis) |
| 設定管理 | @nestjs/config(.env 載入) |
| 驗證 | class-validator + class-transformer |
| 日誌 | nestjs-pino + pino-roll |
| API 文件 | @nestjs/swagger(僅非 production,路徑 /api/docs) |
| 資安 | helmet + NestJS 內建 CORS |
| 流量控制 | @nestjs/throttler |
| 認證 | @nestjs/jwt |
目錄結構
src/
├── core/ # NestJS 生命週期類別 + 基礎設施模組
│ ├── db/
│ │ └── db.module.ts
│ ├── redis/
│ │ └── redis.module.ts
│ ├── logger/
│ │ └── logger.module.ts
│ ├── throttler/
│ │ └── throttler.module.ts
│ ├── filter/
│ │ └── http-exception.filter.ts
│ ├── interceptor/
│ │ └── transform.interceptor.ts
│ ├── pipe/
│ │ └── validation-exception.factory.ts
│ ├── guard/
│ │ └── jwt.guard.ts
│ └── <類型>/ # middleware …
├── common/ # 共用但不掛生命週期的內容
│ ├── token/
│ │ └── tokens.ts # 注入 token 常數(REDIS_CLIENT 等)
│ ├── type/
│ │ ├── response.ts # ApiResponse<T> 統一回傳型別
│ │ └── jwt-payload.ts # JWT payload 型別
│ ├── decorator/
│ │ └── public.decorator.ts # @Public() 跳過 JWT 驗證
│ └── <類型>/ # util、constant …
├── module/ # 功能模組(每個領域一個子目錄)
│ └── <模組名>/
│ ├── dtos/
│ ├── entities/
│ ├── <模組名>.controller.ts
│ ├── <模組名>.service.ts
│ └── <模組名>.module.ts
├── app.module.ts
└── main.ts
core/ — 生命週期類別與基礎設施模組
任何檔案都必須放在對應的子目錄中,不可直接置於 core/ 根層。
| 子目錄 | 內容 |
|---|---|
filter/ |
例外處理,@Catch() |
interceptor/ |
請求/回應攔截,NestInterceptor |
guard/ |
路由守衛,CanActivate |
pipe/ |
輸入轉換與驗證相關,PipeTransform 及 factory |
middleware/ |
Express 中介層,NestMiddleware |
db/ |
TypeORM / PostgreSQL 設定 |
redis/ |
Redis 設定 |
logger/ |
Pino logger 設定 |
throttler/ |
Rate limiting 設定 |
common/ — 共用工具
不掛生命週期、跨模組共用的內容。任何檔案都必須放在對應的子目錄中,不可直接置於 common/ 根層。
| 子目錄 | 內容 |
|---|---|
token/ |
注入 token 常數(REDIS_CLIENT 等) |
type/ |
共用 TypeScript 型別/interface/enum |
decorator/ |
自訂裝飾器 |
util/ |
純函式工具 |
constant/ |
一般常數 |
module/ — 功能模組
每個業務領域一個子目錄,結構依複雜度選擇:
簡單模組(單一 controller / service):
module/<模組名>/
├── dtos/
├── entities/
├── <模組名>.controller.ts
├── <模組名>.service.ts
└── <模組名>.module.ts
複雜模組(多個 controller / service):
module/<模組名>/
├── dtos/
├── entities/
├── controllers/
│ ├── <子功能>.controller.ts
│ └── ...
├── services/
│ ├── <子功能>.service.ts
│ └── ...
└── <模組名>.module.ts
單一檔案與目錄切分可混用:service 需要拆分但 controller 不需要時,controller 維持單檔即可。
全域基礎設施
以下透過 APP_* token 在 AppModule 全域註冊,所有模組自動套用。執行順序如下:
ThrottlerGuard— rate limitingJwtAuthGuard— JWT 驗證ValidationPipe— 請求驗證TransformInterceptor— 回應包裝HttpExceptionFilter— 例外捕捉
統一回傳格式(ApiResponse<T>)
所有端點一律回傳 { code, message, data } 結構:
// 成功
{ "code": 200, "message": "ok", "data": { ... } }
// 失敗
{ "code": 404, "message": "...", "data": null }
TransformInterceptor(core/interceptor/)— 包裝成功回應HttpExceptionFilter(core/filter/)— 捕捉所有例外並統一格式
ValidationPipe
| 選項 | 值 | 說明 |
|---|---|---|
whitelist |
true |
自動剔除 DTO 未宣告的屬性 |
forbidNonWhitelisted |
true |
傳入未宣告屬性時回傳 400 |
transform |
true |
自動將 payload 轉為 DTO 實例 |
exceptionFactory |
validationExceptionFactory |
forbidden 欄位顯示 不允許的欄位:<field> |
JWT 認證
- 全域啟用
JwtAuthGuard,預設所有路由需要有效 Bearer token - 公開路由加上
@Public()decorator 跳過驗證 - Payload 型別定義於
common/type/jwt-payload.ts,sub為 user ID - 需要 Swagger 測試時在端點加上
@ApiBearerAuth()
@Public() // 跳過 JWT
@SkipThrottle() // 跳過 rate limiting(@nestjs/throttler 提供)
Rate Limiting
透過 THROTTLE_TTL(毫秒)與 THROTTLE_LIMIT(次數)設定,預設 60 秒內最多 60 次請求。
日誌策略
使用 nestjs-pino,以 NODE_ENV 切換行為:
| 環境 | 輸出 |
|---|---|
| 非 production | pino-pretty,colorize,輸出至 stdout |
| production | pino-roll 寫入檔案 |
production 檔案規則:
| 類型 | 路徑 | 檔名格式 | 保留天數 |
|---|---|---|---|
| app log | logs/app/ |
app.yyyy-MM-dd.log |
14 天 |
| error log | logs/error/ |
error.yyyy-MM-dd.log |
30 天 |
每筆 request 自動記錄: method、url、query、params、ip(含 X-Forwarded-For)、userAgent、statusCode、responseTime
資安
Helmet
全域套用 helmet() Express middleware,設定標準安全 HTTP headers(CSP、HSTS、X-Frame-Options 等)。
CORS
透過 CORS_ORIGINS 環境變數控制允許的來源,多個來源以逗號分隔。預設空陣列(全封)。
| 設定 | 值 |
|---|---|
methods |
GET、POST、PUT、PATCH、DELETE |
credentials |
true |
Swagger
- 僅在非 production 環境啟用,路徑為
/api/docs - 預設已加入
addBearerAuth(),需要保護的端點加上@ApiBearerAuth() - DTO 屬性使用
@ApiProperty()補充文件
環境變數
參考 .env.example:
NODE_ENV=development
PORT=3000
CORS_ORIGINS=http://localhost:5173,http://localhost:3000
DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=postgres
DB_NAME=nest_db
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_USER= # 選填
REDIS_PASSWORD= # 選填
JWT_SECRET=change-me
JWT_EXPIRES_IN=7d
THROTTLE_TTL=60000 # 毫秒
THROTTLE_LIMIT=60 # 次數
編碼規範
- 所有相對路徑 import 必須加
.js副檔名(NodeNext 規定) - 禁止
any,型別須明確宣告 - 無註解 — 程式碼透過命名與結構自我表達
- 正式程式碼不留
console.log(使用注入的Logger) - 類別屬性在適用情況下使用
readonly
常用指令
pnpm start:dev # 開發模式(watch)
pnpm build # 編譯
pnpm start:prod # 執行編譯後的產物
pnpm test # 單元測試
pnpm test:cov # 測試覆蓋率