NestJS 11 Best Practices
Quick Reference
| Topic |
When to Use |
Reference |
| Core Architecture |
Modules, Providers, DI, forwardRef, custom decorators |
core-architecture.md |
| Request Lifecycle |
Middleware, Guards, Interceptors, Pipes, Filters |
request-lifecycle.md |
| Validation & Pipes |
DTOs, class-validator, ValidationPipe, transforms |
validation-pipes.md |
| Authentication |
JWT, Passport, Guards, Local/OAuth strategies, RBAC |
authentication.md |
| Database |
TypeORM, Prisma, Drizzle ORM, repository patterns |
database-integration.md |
| Testing |
Unit tests, E2E tests, mocking providers |
testing.md |
| OpenAPI & GraphQL |
Swagger decorators, resolvers, subscriptions |
openapi-graphql.md |
| Microservices |
TCP, Redis, NATS, Kafka patterns |
microservices.md |
Essential Patterns
Module with Providers
@Module({
imports: [DatabaseModule],
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService], // Export for other modules
})
export class UsersModule {}
Controller with Validation
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Post()
create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.usersService.findOne(id);
}
}
DTO with Validation
import { IsEmail, IsString, MinLength, IsOptional } from 'class-validator';
export class CreateUserDto {
@IsEmail()
email: string;
@IsString()
@MinLength(8)
password: string;
@IsOptional()
@IsString()
name?: string;
}
Exception Filter
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const status = exception.getStatus();
response.status(status).json({
statusCode: status,
message: exception.message,
timestamp: new Date().toISOString(),
});
}
}
Guard with JWT
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
canActivate(context: ExecutionContext) {
return super.canActivate(context);
}
}
NestJS 11 Breaking Changes
- Express v5: Wildcards must be named (e.g.,
*splat), optional params use braces /:file{.:ext}
- Node.js 20+: Minimum required version
- Fastify v5: Updated adapter for Fastify users
- Dynamic Modules: Same module with identical config imported multiple times = separate instances
Common Mistakes
- Not using
forwardRef() for circular deps - Causes "cannot resolve dependency" errors; wrap in forwardRef(() => ModuleName)
- Throwing plain errors instead of HttpException - Loses status codes, breaks exception filters; use
throw new BadRequestException('message')
- Missing
@Injectable() decorator - Provider won't be injectable; always decorate services
- Global ValidationPipe without
whitelist: true - Allows unexpected properties; set whitelist: true, forbidNonWhitelisted: true
- Importing modules instead of exporting providers - Use
exports array to share providers across modules
- Async config without
ConfigModule.forRoot() - ConfigService undefined; import ConfigModule in AppModule
- Testing without
overrideProvider() - Uses real services in unit tests; mock dependencies with overrideProvider(Service).useValue(mock)
- E2E tests sharing database state - No isolation between tests; use transactions or truncate tables in beforeEach
1---2name: nestjs-best-practices3description: NestJS 11+ best practices for enterprise Node.js applications with TypeScript. Use when writing, reviewing, or refactoring NestJS controllers, services, modules, or APIs. Triggers on: NestJS modules, controllers, providers, dependency injection, @Injectable, @Controller, @Module, middleware, guards, interceptors, pipes, exception filters, ValidationPipe, class-validator, class-transformer, DTOs, JWT authentication, Passport strategies, @nestjs/passport, TypeORM entities, Prisma client, Drizzle ORM, repository pattern, circular dependencies, forwardRef, @nestjs/swagger, OpenAPI decorators, GraphQL resolvers, @nestjs/graphql, microservices, TCP transport, Redis transport, NATS, Kafka, NestJS 11 breaking changes, Express v5 migration, custom decorators, ConfigService, @nestjs/config, health checks, or NestJS testing patterns.4license: MIT5---67# NestJS 11 Best Practices89## Quick Reference1011| Topic | When to Use | Reference |12|-------|-------------|-----------|13| **Core Architecture** | Modules, Providers, DI, forwardRef, custom decorators | [core-architecture.md](references/core-architecture.md) |14| **Request Lifecycle** | Middleware, Guards, Interceptors, Pipes, Filters | [request-lifecycle.md](references/request-lifecycle.md) |15| **Validation & Pipes** | DTOs, class-validator, ValidationPipe, transforms | [validation-pipes.md](references/validation-pipes.md) |16| **Authentication** | JWT, Passport, Guards, Local/OAuth strategies, RBAC | [authentication.md](references/authentication.md) |17| **Database** | TypeORM, Prisma, Drizzle ORM, repository patterns | [database-integration.md](references/database-integration.md) |18| **Testing** | Unit tests, E2E tests, mocking providers | [testing.md](references/testing.md) |19| **OpenAPI & GraphQL** | Swagger decorators, resolvers, subscriptions | [openapi-graphql.md](references/openapi-graphql.md) |20| **Microservices** | TCP, Redis, NATS, Kafka patterns | [microservices.md](references/microservices.md) |2122## Essential Patterns2324### Module with Providers2526```typescript27@Module({28 imports: [DatabaseModule],29 controllers: [UsersController],30 providers: [UsersService],31 exports: [UsersService], // Export for other modules32})33export class UsersModule {}34```3536### Controller with Validation3738```typescript39@Controller('users')40export class UsersController {41 constructor(private readonly usersService: UsersService) {}4243 @Post()44 create(@Body() createUserDto: CreateUserDto) {45 return this.usersService.create(createUserDto);46 }4748 @Get(':id')49 findOne(@Param('id', ParseIntPipe) id: number) {50 return this.usersService.findOne(id);51 }52}53```5455### DTO with Validation5657```typescript58import { IsEmail, IsString, MinLength, IsOptional } from 'class-validator';5960export class CreateUserDto {61 @IsEmail()62 email: string;6364 @IsString()65 @MinLength(8)66 password: string;6768 @IsOptional()69 @IsString()70 name?: string;71}72```7374### Exception Filter7576```typescript77@Catch(HttpException)78export class HttpExceptionFilter implements ExceptionFilter {79 catch(exception: HttpException, host: ArgumentsHost) {80 const ctx = host.switchToHttp();81 const response = ctx.getResponse<Response>();82 const status = exception.getStatus();8384 response.status(status).json({85 statusCode: status,86 message: exception.message,87 timestamp: new Date().toISOString(),88 });89 }90}91```9293### Guard with JWT9495```typescript96@Injectable()97export class JwtAuthGuard extends AuthGuard('jwt') {98 canActivate(context: ExecutionContext) {99 return super.canActivate(context);100 }101}102```103104## NestJS 11 Breaking Changes105106- **Express v5**: Wildcards must be named (e.g., `*splat`), optional params use braces `/:file{.:ext}`107- **Node.js 20+**: Minimum required version108- **Fastify v5**: Updated adapter for Fastify users109- **Dynamic Modules**: Same module with identical config imported multiple times = separate instances110111## Common Mistakes1121131. **Not using `forwardRef()` for circular deps** - Causes "cannot resolve dependency" errors; wrap in `forwardRef(() => ModuleName)`1142. **Throwing plain errors instead of HttpException** - Loses status codes, breaks exception filters; use `throw new BadRequestException('message')`1153. **Missing `@Injectable()` decorator** - Provider won't be injectable; always decorate services1164. **Global ValidationPipe without `whitelist: true`** - Allows unexpected properties; set `whitelist: true, forbidNonWhitelisted: true`1175. **Importing modules instead of exporting providers** - Use `exports` array to share providers across modules1186. **Async config without `ConfigModule.forRoot()`** - ConfigService undefined; import ConfigModule in AppModule1197. **Testing without `overrideProvider()`** - Uses real services in unit tests; mock dependencies with `overrideProvider(Service).useValue(mock)`1208. **E2E tests sharing database state** - No isolation between tests; use transactions or truncate tables in beforeEach