| name | nestjs |
| description | NestJS 11 patterns, modules, decorators, guards, pipes, CQRS. Trigger: When writing NestJS backend code - controllers, services, modules, DTOs.
|
| license | Apache-2.0 |
| metadata | {"author":"gentleman-programming","version":"1.0"} |
When to Use
- Creating or modifying NestJS controllers, services, modules
- Writing DTOs with class-validator
- Implementing guards, interceptors, pipes, exception filters
- Setting up authentication (JWT/Passport)
- Working with CQRS pattern
- Writing NestJS unit or e2e tests
Module Organization (Feature-Based)
src/
├── main.ts
├── app.module.ts
├── common/ # Shared guards, interceptors, pipes, filters
│ ├── decorators/
│ ├── guards/
│ ├── interceptors/
│ ├── pipes/
│ └── filters/
├── config/
├── modules/
│ └── users/
│ ├── users.module.ts
│ ├── users.controller.ts
│ ├── users.service.ts
│ ├── dto/
│ │ ├── create-user.dto.ts
│ │ └── update-user.dto.ts
│ └── entities/
└── shared/ # Global modules (database, cache)
└── prisma/
Critical Patterns
Controller + Service
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Post()
create(@Body() dto: CreateUserDto) {
return this.usersService.create(dto);
}
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.usersService.findOne(id);
}
}
DTOs with class-validator (REQUIRED)
import { IsEmail, IsNotEmpty, IsString, MinLength, IsOptional } from 'class-validator';
import { Transform } from 'class-transformer';
export class CreateUserDto {
@IsString()
@IsNotEmpty()
name: string;
@IsEmail()
email: string;
@IsString()
@MinLength(8)
password: string;
@IsOptional()
@Transform(({ value }) => value?.trim())
bio?: string;
}
Global ValidationPipe (main.ts)
app.useGlobalPipes(new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}));
Execution Order
Middleware → Guards → Before Interceptors → Pipes → Controller → Service → After Interceptors → Exception Filters
Guard
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const roles = this.reflector.get<string[]>('roles', context.getHandler());
if (!roles) return true;
const request = context.switchToHttp().getRequest();
return roles.some(role => request.user.roles?.includes(role));
}
}
Custom Decorator
export const CurrentUser = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
return ctx.switchToHttp().getRequest().user;
},
);
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
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,
timestamp: new Date().toISOString(),
path: ctx.getRequest<Request>().url,
message: exception.message,
});
}
}
Authentication (JWT + Passport)
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: process.env.JWT_SECRET,
});
}
async validate(payload: any) {
return { userId: payload.sub, email: payload.email };
}
}
@Module({
imports: [
PassportModule,
JwtModule.register({
secret: process.env.JWT_SECRET,
signOptions: { expiresIn: '1h' },
}),
],
providers: [AuthService, JwtStrategy, LocalStrategy],
})
export class AuthModule {}
CQRS Pattern
export class CreateUserCommand {
constructor(public readonly name: string, public readonly email: string) {}
}
@CommandHandler(CreateUserCommand)
export class CreateUserHandler implements ICommandHandler<CreateUserCommand> {
async execute(command: CreateUserCommand) { }
}
export class GetUserQuery {
constructor(public readonly id: number) {}
}
@QueryHandler(GetUserQuery)
export class GetUserHandler implements IQueryHandler<GetUserQuery> {
async execute(query: GetUserQuery) { }
}
export class UserCreatedEvent {
constructor(public readonly userId: number) {}
}
@EventsHandler(UserCreatedEvent)
export class UserCreatedHandler implements IEventHandler<UserCreatedEvent> {
handle(event: UserCreatedEvent) { }
}
Testing
describe('UsersService', () => {
let service: UsersService;
let prisma: PrismaService;
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [
UsersService,
{ provide: PrismaService, useValue: { user: { create: jest.fn(), findUnique: jest.fn() } } },
],
}).compile();
service = module.get(UsersService);
prisma = module.get(PrismaService);
});
});
describe('UsersController (e2e)', () => {
let app: INestApplication;
beforeEach(async () => {
const module = await Test.createTestingModule({ imports: [AppModule] }).compile();
app = module.createNestApplication();
await app.init();
});
it('/users (POST)', () => {
return request(app.getHttpServer())
.post('/users')
.send({ name: 'Test', email: 'test@test.com' })
.expect(201);
});
});
Decision Tree
Need authentication? → JWT + Passport strategy
Need authorization? → Custom Guard + Roles decorator
Need request validation? → DTO + class-validator + ValidationPipe
Need response transform? → Interceptor
Need error formatting? → Exception Filter
Need request logging? → Middleware or Interceptor
Need CQRS? → @nestjs/cqrs (commands, queries, events)
Commands
nest new project-name
nest g res modules/users
nest g mo modules/auth
nest g co modules/auth
nest g s modules/auth
nest g guard common/guards/roles
npm run start:dev
npm run test
npm run test:e2e
NestJS 11 Notes
- Express v5: Wildcards must be named (
*splat not *)
ParseDatePipe available in @nestjs/common
- JSON logging built-in for containers
- Prisma integration: set
moduleFormat = "cjs" in schema.prisma (Prisma 7 is ESM default)