| name | nestjs-best-practices |
| description | Comprehensive NestJS best practices for building production-grade backend applications. Use when Claude needs to write, review, scaffold, or refactor NestJS code. Triggers on any mention of "NestJS", "Nest.js", "@nestjs/", NestJS decorators (@Controller, @Injectable, @Module, @Guard, @Interceptor), NestJS CLI commands (nest new, nest generate), or requests involving NestJS modules, providers, controllers, services, DTOs, guards, interceptors, pipes, middleware, exception filters, ConfigModule, TypeORM/Prisma/MikroORM with NestJS, Passport/JWT auth in NestJS, @nestjs/swagger, @nestjs/cqrs, @nestjs/microservices, @nestjs/websockets, @nestjs/graphql, @nestjs/bullmq, @nestjs/terminus, @nestjs/throttler, or NestJS testing with Jest. Also triggers for NestJS project structure decisions, NestJS Docker/deployment, and NestJS performance optimization. Use when this capability is needed. |
| metadata | {"author":"BonsaiSoftware"} |
NestJS Best Practices
Production-grade patterns for NestJS applications (2024–2025). Rules are organized by domain
and rated by impact: CRITICAL (causes bugs/vulnerabilities if ignored), HIGH (significant
quality impact), MEDIUM (recommended convention).
Rule Categories by Priority
Quick Reference — CRITICAL Rules
These rules must always be followed. Violating them causes security vulnerabilities, data loss,
or production failures.
Architecture
- Feature-based module organization — Group by business domain, not by layer. Each module
owns its controllers, services, DTOs, entities. Never put all controllers in
/controllers.
- No circular dependencies — Redesign with a shared service or events instead of
forwardRef(). If unavoidable, use forwardRef() on both sides.
Validation
- Global ValidationPipe with whitelist — Always set
whitelist: true and
forbidNonWhitelisted: true. Without this, clients can inject arbitrary properties.
- DTOs must be classes, not interfaces — Decorators only work on classes. Interfaces are
erased at runtime and provide zero validation.
- Nested validation requires @Type —
@ValidateNested() alone does nothing without
@Type(() => NestedDto) from class-transformer.
Security
- Never use
origin: '*' for CORS in production — Specify allowed origins explicitly.
- Always hash passwords with bcrypt/argon2 — Never store plaintext passwords.
- Short-lived access tokens (≤15min) — Use refresh token rotation for session persistence.
- Rate-limit authentication endpoints — Use stricter
@Throttle() on login/register.
Database
- Disable
synchronize: true in production — Use migrations. Synchronize can drop columns
and lose data.
- Always release QueryRunner in finally block — Unreleased connections cause pool exhaustion.
Config
- Validate env vars at startup — Use Joi or class-validator schema. Fail fast, not at
first request.
- Never access
process.env directly — Use ConfigService or typed namespace injection.
Deployment
- Use
CMD ["node", "dist/main.js"] not npm start — npm doesn't forward SIGTERM,
preventing graceful shutdown.
- Enable shutdown hooks — Call
app.enableShutdownHooks() and implement
OnApplicationShutdown for connection cleanup.
Quick Reference — HIGH Rules
Architecture
- Keep controllers thin — HTTP concerns only, delegate logic to services.
- Use barrel exports (
index.ts) per module for clean imports.
- Limit
@Global() to truly universal services (config, logging).
Providers & DI
- Default to singleton scope — REQUEST scope has ~15% overhead and propagates.
- Register guards/pipes/filters via module providers (
APP_GUARD, APP_PIPE, APP_FILTER)
not app.useGlobal*() — module registration supports dependency injection.
Error Handling
- Use a single global
AllExceptionsFilter for consistent error shape.
- Prefer NestJS built-in exceptions (
NotFoundException, ConflictException) over raw
HttpException.
Auth
- Separate access and refresh token secrets.
- Store refresh tokens hashed (argon2/bcrypt) in database.
- Use HTTP-only cookies for refresh tokens to mitigate XSS.
Database
- Use Data Mapper pattern over Active Record for testability (TypeORM).
- Configure connection pooling (
extra: { max: 20, min: 5 }).
- Use
prisma migrate deploy in production, never prisma db push.
Testing
- Follow Arrange-Act-Assert structure for all tests.
- Co-locate unit tests (
*.spec.ts) with source files; E2E in /test.
- Use
Test.createTestingModule with mocked providers — don't import real modules.
Config & Logging
- Use Pino (
nestjs-pino) for production logging — fastest Node.js logger.
- Implement separate liveness and readiness health endpoints with
@nestjs/terminus.
Performance
- Use Fastify adapter for throughput-critical services (~2x over Express).
- Lazy-load infrequently used modules with
LazyModuleLoader.
- Use
cache: true on ConfigModule — process.env access is slow.
When to Read Reference Files
IMPORTANT: Do NOT read the compiled guide. Read only the 1-2 reference files relevant to the current task.
- Identify the domain from the mapping below
- Read only the matching file(s) from
references/
- Typically 1-2 reference files are relevant per task
| Task | Read |
|---|
| Creating/scaffolding project or module | references/architecture.md |
| Writing services, providers, DI issues | references/providers-and-di.md |
| Creating DTOs, validation, pipes | references/validation-and-dtos.md |
| Error handling or exception filters | references/error-handling.md |
| Auth, authorization, security | references/auth-and-security.md |
| Database, ORM, queries | references/database.md |
| Environment config, logging, health checks | references/config-and-logging.md |
| Writing or improving tests | references/testing.md |
| CQRS, microservices, WebSockets, GraphQL, queues, caching | references/advanced-patterns.md |
| Dockerizing, deploying, performance | references/deployment.md |
Essential Code Patterns
Correct main.ts bootstrap
import { NestFactory } from '@nestjs/core';
import { ValidationPipe, VersioningType } from '@nestjs/common';
import { AppModule } from './app.module';
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.enableVersioning({ type: VersioningType.URI, defaultVersion: '1' });
app.enableCors({ origin: process.env.ALLOWED_ORIGINS?.split(',') });
app.enableShutdownHooks();
await app.listen(process.. ?? );
}
();
Correct module structure
@Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
Correct controller pattern (thin)
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Post()
@HttpCode(HttpStatus.CREATED)
create(@Body() dto: CreateUserDto) {
return this.usersService.create(dto);
}
@Get(':id')
findOne(@Param('id', ParseUUIDPipe) id: string) {
return this.usersService.findOneOrFail(id);
}
}
Naming conventions
| Element | Convention | Example |
|---|
| Files | kebab-case.<type>.ts | create-user.dto.ts |
| Classes | PascalCase + type suffix | CreateUserDto, AuthGuard |
| Modules | <Feature>Module | UsersModule |
| Services | <Feature>Service | UsersService |
| Controllers | <Feature>Controller | UsersController |
| Entities | singular PascalCase | User, OrderItem |
| Test files | *.spec.ts (unit), *.e2e-spec.ts (E2E) | users.service.spec.ts |
NestJS Request Lifecycle
Request → Middleware → Guards → Interceptors (pre) → Pipes → Handler → Interceptors (post) → Filters (on error)
Use this to decide where logic belongs:
- Middleware: Logging, CORS, request ID — no access to handler context.
- Guards: Auth, RBAC — have
ExecutionContext, block before interceptors.
- Interceptors: Response transform, timing, caching — wrap handler with RxJS.
- Pipes: Per-parameter validation and transformation.
- Filters: Error formatting — catch exceptions from any layer above.
Source: BonsaiSoftware/bonsaipowers — distributed by TomeVault.