nestjs-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseNestJS Development Patterns
NestJS开发模式
Production-grade NestJS patterns for modular TypeScript backends.
用于构建模块化TypeScript后端的生产级NestJS模式。
When to Activate
何时启用
- Building NestJS APIs or services
- Structuring modules, controllers, and providers
- Adding DTO validation, guards, interceptors, or exception filters
- Configuring environment-aware settings and database integrations
- Testing NestJS units or HTTP endpoints
- 构建NestJS API或服务时
- 搭建模块、控制器和提供者的结构时
- 添加DTO验证、守卫、拦截器或异常过滤器时
- 配置环境感知设置和数据库集成时
- 测试NestJS单元或HTTP端点时
Project Structure
项目结构
text
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/- Keep domain code inside feature modules.
- Put cross-cutting filters, decorators, guards, and interceptors in .
common/ - Keep DTOs close to the module that owns them.
text
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与所属模块尽量放在一起。
Bootstrap and Global Validation
启动引导与全局验证
ts
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();- Always enable and
whiteliston public APIs.forbidNonWhitelisted - Prefer one global validation pipe instead of repeating validation config per route.
ts
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 - 优先使用全局校验管道,避免为每个路由重复配置校验规则。
Modules, Controllers, and Providers
模块、控制器与提供者
ts
@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);
}
}- Controllers should stay thin: parse HTTP input, call a provider, return response DTOs.
- Put business logic in injectable services, not controllers.
- Export only the providers other modules genuinely need.
ts
@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。
- 将业务逻辑放在可注入的服务中,而非控制器里。
- 仅导出其他模块确实需要的提供者。
DTOs and Validation
DTO与验证
ts
export class CreateUserDto {
@IsEmail()
email!: string;
@IsString()
@Length(2, 80)
name!: string;
@IsOptional()
@IsEnum(UserRole)
role?: UserRole;
}- Validate every request DTO with .
class-validator - Use dedicated response DTOs or serializers instead of returning ORM entities directly.
- Avoid leaking internal fields such as password hashes, tokens, or audit columns.
ts
export class CreateUserDto {
@IsEmail()
email!: string;
@IsString()
@Length(2, 80)
name!: string;
@IsOptional()
@IsEnum(UserRole)
role?: UserRole;
}- 用验证每个请求DTO。
class-validator - 使用专门的响应DTO或序列化器,不要直接返回ORM实体。
- 避免泄露密码哈希、令牌或审计列这类内部字段。
Auth, Guards, and Request Context
鉴权、守卫与请求上下文
ts
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
@Get('admin/report')
getAdminReport(@Req() req: AuthenticatedRequest) {
return this.reportService.getForUser(req.user.id);
}- Keep auth strategies and guards module-local unless they are truly shared.
- Encode coarse access rules in guards, then do resource-specific authorization in services.
- Prefer explicit request types for authenticated request objects.
ts
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
@Get('admin/report')
getAdminReport(@Req() req: AuthenticatedRequest) {
return this.reportService.getForUser(req.user.id);
}- 鉴权策略和守卫除非确实需要共享,否则放在模块内部。
- 在守卫中定义粗粒度的访问规则,在服务中实现资源专属的授权逻辑。
- 优先为已认证的请求对象使用显式的请求类型。
Exception Filters and Error Shape
异常过滤器与错误格式
ts
@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',
});
}
}- Keep one consistent error envelope across the API.
- Throw framework exceptions for expected client errors; log and wrap unexpected failures centrally.
ts
@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保持统一的错误返回结构。
- 预期的客户端错误抛出框架异常,非预期的失败统一在中心层记录日志并包装。
Config and Environment Validation
配置与环境变量验证
ts
ConfigModule.forRoot({
isGlobal: true,
load: [configuration],
validate: validateEnv,
});- Validate env at boot, not lazily at first request.
- Keep config access behind typed helpers or config services.
- Split dev/staging/prod concerns in config factories instead of branching throughout feature code.
ts
ConfigModule.forRoot({
isGlobal: true,
load: [configuration],
validate: validateEnv,
});- 在服务启动时校验环境变量,不要等到首次请求时才懒加载校验。
- 通过带类型的辅助方法或配置服务来访问配置。
- 在配置工厂中拆分开发/预发/生产环境的逻辑,不要在功能代码中到处做分支判断。
Persistence and Transactions
持久化与事务
- Keep repository / ORM code behind providers that speak domain language.
- For Prisma or TypeORM, isolate transactional workflows in services that own the unit of work.
- Do not let controllers coordinate multi-step writes directly.
- 把仓库/ORM代码封装在使用领域语言的提供者之后。
- 对于Prisma或TypeORM,将事务流程隔离在负责工作单元的服务中。
- 不要让控制器直接协调多步写入操作。
Testing
测试
ts
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();
});
});- Unit test providers in isolation with mocked dependencies.
- Add request-level tests for guards, validation pipes, and exception filters.
- Reuse the same global pipes/filters in tests that you use in production.
ts
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();
});
});- 单元测试时通过Mock依赖单独测试提供者。
- 为守卫、校验管道和异常过滤器添加请求级别的测试。
- 在测试中复用生产环境使用的全局管道/过滤器。
Production Defaults
生产环境默认配置
- Enable structured logging and request correlation ids.
- Terminate on invalid env/config instead of booting partially.
- Prefer async provider initialization for DB/cache clients with explicit health checks.
- Keep background jobs and event consumers in their own modules, not inside HTTP controllers.
- Make rate limiting, auth, and audit logging explicit for public endpoints.
- 开启结构化日志和请求关联ID。
- 遇到无效的环境变量/配置时直接终止服务,不要部分启动。
- 数据库/缓存客户端优先使用异步提供者初始化,并附带明确的健康检查。
- 后台任务和事件消费者放在独立模块中,不要放在HTTP控制器里。
- 公共端点要明确配置限流、鉴权和审计日志。