SKILL.md
readonlyread-only
name
nestjs-patterns
description
NestJS 架構模式,涵蓋模組、控制器、提供者、DTO 驗證、守衛、攔截器、設定檔以及生產級 TypeScript 後端。
NestJS 開發模式
生產級 NestJS 模式,用於模組化 TypeScript 後端。
啟用時機
- 建置 NestJS API 或服務
- 組織模組、控制器和提供者
- 加入 DTO 驗證、守衛、攔截器或例外過濾器
- 設定環境感知設定和資料庫整合
- 測試 NestJS 單元或 HTTP 端點
專案結構
src/
├── app.module.ts
├── main.ts
├── common/
│ ├── filters/
│ ├── guards/
│ ├── interceptors/
│ └── pipes/
├── config/
│ ├── configuration.ts
│ └── validation.ts
├── modules/
│ ├── auth/
│ │ ├── auth.controller.ts
│ │ ├── auth.module.ts
│ │ ├── auth.service.ts
│ │ ├── dto/
│ │ ├── guards/
│ │ └── strategies/
│ └── users/
│ ├── dto/
│ ├── entities/
│ ├── users.controller.ts
│ ├── users.module.ts
│ └── users.service.ts
└── prisma/ or database/
- 將領域程式碼保留在功能模組內。
- 將橫切關注的過濾器、裝飾器、守衛和攔截器放在
common/中。 - 讓 DTO 靠近其所屬的模組。
啟動與全域驗證
async function bootstrap() {
const app = await NestFactory.create(AppModule, { bufferLogs: true });
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
transformOptions: { enableImplicitConversion: true },
}),
);
app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector)));
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
- 在公開 API 上務必啟用
whitelist和forbidNonWhitelisted。 - 偏好使用單一全域驗證管道,而非在每個路由重複驗證設定。
模組、控制器與提供者
@Module({
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get(':id')
getById(@Param('id', ParseUUIDPipe) id: string) {
return this.usersService.getById(id);
}
@Post()
create(@Body() dto: CreateUserDto) {
return this.usersService.create(dto);
}
}
@Injectable()
export class UsersService {
constructor(private readonly usersRepo: UsersRepository) {}
async create(dto: CreateUserDto) {
return this.usersRepo.create(dto);
}
}
- 控制器應保持輕薄:解析 HTTP 輸入、呼叫提供者、回傳回應 DTO。
- 將商業邏輯放在可注入的服務中,而非控制器。
- 僅匯出其他模組真正需要的提供者。
DTO 與驗證
export class CreateUserDto {
@IsEmail()
email!: string;
@IsString()
@Length(2, 80)
name!: string;
@IsOptional()
@IsEnum(UserRole)
role?: UserRole;
}
- 使用
class-validator驗證每個請求 DTO。 - 使用專用的回應 DTO 或序列化器,而非直接回傳 ORM 實體。
- 避免洩漏內部欄位,例如密碼雜湊、令牌或稽核欄位。
認證、守衛與請求上下文
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
@Get('admin/report')
getAdminReport(@Req() req: AuthenticatedRequest) {
return this.reportService.getForUser(req.user.id);
}
- 將認證策略和守衛保留在模組層級,除非它們是真正共用的。
- 在守衛中編寫粗略的存取規則,然後在服務中進行資源特定的授權。
- 對已認證的請求物件偏好使用明確的請求型別。
例外過濾器與錯誤格式
@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const response = host.switchToHttp().getResponse<Response>();
const request = host.switchToHttp().getRequest<Request>();
if (exception instanceof HttpException) {
return response.status(exception.getStatus()).json({
path: request.url,
error: exception.getResponse(),
});
}
return response.status(500).json({
path: request.url,
error: 'Internal server error',
});
}
}
- 在整個 API 中保持一致的錯誤封裝格式。
- 對預期的客戶端錯誤拋出框架例外;集中記錄並包裝非預期的失敗。
設定檔與環境驗證
ConfigModule.forRoot({
isGlobal: true,
load: [configuration],
validate: validateEnv,
});
- 在啟動時驗證環境變數,而非在第一次請求時延遲驗證。
- 透過型別輔助函式或設定服務來存取設定。
- 在設定工廠中區分開發/測試/正式環境的關注點,而非在功能程式碼中分支。
持久化與交易
- 將儲存庫 / ORM 程式碼放在使用領域語言溝通的提供者後面。
- 對於 Prisma 或 TypeORM,將交易工作流程隔離在擁有工作單元的服務中。
- 不要讓控制器直接協調多步驟寫入。
測試
describe('UsersController', () => {
let app: INestApplication;
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
imports: [UsersModule],
}).compile();
app = moduleRef.createNestApplication();
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
await app.init();
});
});
- 使用模擬的依賴項對提供者進行單元測試。
- 為守衛、驗證管道和例外過濾器加入請求層級的測試。
- 在測試中重複使用與生產環境相同的全域管道/過濾器。
生產環境預設
- 啟用結構化日誌和請求關聯 ID。
- 在無效的環境/設定時終止,而非部分啟動。
- 偏好非同步提供者初始化資料庫/快取客戶端,並搭配明確的健康檢查。
- 將背景工作和事件消費者放在自己的模組中,而非 HTTP 控制器內。
- 對公開端點明確啟用速率限制、認證和稽核日誌。






