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
| Category |
CRITICAL |
HIGH |
MEDIUM |
Reference |
| Architecture & Modules |
2 |
3 |
2 |
architecture.md |
| Providers & DI |
2 |
2 |
2 |
providers-and-di.md |
| Validation & DTOs |
3 |
2 |
1 |
validation-and-dtos.md |
| Error Handling |
2 |
2 |
1 |
error-handling.md |
| Auth & Security |
4 |
3 |
1 |
auth-and-security.md |
| Database |
2 |
3 |
2 |
database.md |
| Config & Logging |
2 |
2 |
2 |
config-and-logging.md |
| Testing |
1 |
3 |
2 |
testing.md |
| Advanced Patterns |
1 |
4 |
3 |
advanced-patterns.md |
| Deployment & Perf |
2 |
3 |
2 |
deployment.md |
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.env.PORT ?? 3000);
}
bootstrap();
Correct module structure
// users/users.module.ts
@Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService], // only export what other modules need
})
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.
1---2name: nestjs-best-practices-23description: 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.4---56# NestJS Best Practices78Production-grade patterns for NestJS applications (2024–2025). Rules are organized by domain9and rated by impact: **CRITICAL** (causes bugs/vulnerabilities if ignored), **HIGH** (significant10quality impact), **MEDIUM** (recommended convention).1112## Rule Categories by Priority1314| Category | CRITICAL | HIGH | MEDIUM | Reference |15|---|---|---|---|---|16| Architecture & Modules | 2 | 3 | 2 | [architecture.md](references/architecture.md) |17| Providers & DI | 2 | 2 | 2 | [providers-and-di.md](references/providers-and-di.md) |18| Validation & DTOs | 3 | 2 | 1 | [validation-and-dtos.md](references/validation-and-dtos.md) |19| Error Handling | 2 | 2 | 1 | [error-handling.md](references/error-handling.md) |20| Auth & Security | 4 | 3 | 1 | [auth-and-security.md](references/auth-and-security.md) |21| Database | 2 | 3 | 2 | [database.md](references/database.md) |22| Config & Logging | 2 | 2 | 2 | [config-and-logging.md](references/config-and-logging.md) |23| Testing | 1 | 3 | 2 | [testing.md](references/testing.md) |24| Advanced Patterns | 1 | 4 | 3 | [advanced-patterns.md](references/advanced-patterns.md) |25| Deployment & Perf | 2 | 3 | 2 | [deployment.md](references/deployment.md) |2627## Quick Reference — CRITICAL Rules2829These rules must always be followed. Violating them causes security vulnerabilities, data loss,30or production failures.3132### Architecture33341. **Feature-based module organization** — Group by business domain, not by layer. Each module35 owns its controllers, services, DTOs, entities. Never put all controllers in `/controllers`.362. **No circular dependencies** — Redesign with a shared service or events instead of37 `forwardRef()`. If unavoidable, use `forwardRef()` on both sides.3839### Validation40413. **Global ValidationPipe with whitelist** — Always set `whitelist: true` and42 `forbidNonWhitelisted: true`. Without this, clients can inject arbitrary properties.434. **DTOs must be classes, not interfaces** — Decorators only work on classes. Interfaces are44 erased at runtime and provide zero validation.455. **Nested validation requires @Type** — `@ValidateNested()` alone does nothing without46 `@Type(() => NestedDto)` from class-transformer.4748### Security49506. **Never use `origin: '*'` for CORS in production** — Specify allowed origins explicitly.517. **Always hash passwords with bcrypt/argon2** — Never store plaintext passwords.528. **Short-lived access tokens (≤15min)** — Use refresh token rotation for session persistence.539. **Rate-limit authentication endpoints** — Use stricter `@Throttle()` on login/register.5455### Database565710. **Disable `synchronize: true` in production** — Use migrations. Synchronize can drop columns58 and lose data.5911. **Always release QueryRunner in finally block** — Unreleased connections cause pool exhaustion.6061### Config626312. **Validate env vars at startup** — Use Joi or class-validator schema. Fail fast, not at64 first request.6513. **Never access `process.env` directly** — Use ConfigService or typed namespace injection.6667### Deployment686914. **Use `CMD ["node", "dist/main.js"]` not `npm start`** — npm doesn't forward SIGTERM,70 preventing graceful shutdown.7115. **Enable shutdown hooks** — Call `app.enableShutdownHooks()` and implement72 `OnApplicationShutdown` for connection cleanup.7374## Quick Reference — HIGH Rules7576### Architecture777816. Keep controllers thin — HTTP concerns only, delegate logic to services.7917. Use barrel exports (`index.ts`) per module for clean imports.8018. Limit `@Global()` to truly universal services (config, logging).8182### Providers & DI838419. Default to singleton scope — REQUEST scope has ~15% overhead and propagates.8520. Register guards/pipes/filters via module providers (`APP_GUARD`, `APP_PIPE`, `APP_FILTER`)86 not `app.useGlobal*()` — module registration supports dependency injection.8788### Error Handling899021. Use a single global `AllExceptionsFilter` for consistent error shape.9122. Prefer NestJS built-in exceptions (`NotFoundException`, `ConflictException`) over raw92 `HttpException`.9394### Auth959623. Separate access and refresh token secrets.9724. Store refresh tokens hashed (argon2/bcrypt) in database.9825. Use HTTP-only cookies for refresh tokens to mitigate XSS.99100### Database10110226. Use Data Mapper pattern over Active Record for testability (TypeORM).10327. Configure connection pooling (`extra: { max: 20, min: 5 }`).10428. Use `prisma migrate deploy` in production, never `prisma db push`.105106### Testing10710829. Follow Arrange-Act-Assert structure for all tests.10930. Co-locate unit tests (`*.spec.ts`) with source files; E2E in `/test`.11031. Use `Test.createTestingModule` with mocked providers — don't import real modules.111112### Config & Logging11311432. Use Pino (`nestjs-pino`) for production logging — fastest Node.js logger.11533. Implement separate liveness and readiness health endpoints with `@nestjs/terminus`.116117### Performance11811934. Use Fastify adapter for throughput-critical services (~2x over Express).12035. Lazy-load infrequently used modules with `LazyModuleLoader`.12136. Use `cache: true` on ConfigModule — `process.env` access is slow.122123## When to Read Reference Files124125**IMPORTANT: Do NOT read the compiled guide. Read only the 1-2 reference files relevant to the current task.**1261271. Identify the domain from the mapping below1282. Read only the matching file(s) from `references/`1293. Typically 1-2 reference files are relevant per task130131| Task | Read |132|---|---|133| Creating/scaffolding project or module | `references/architecture.md` |134| Writing services, providers, DI issues | `references/providers-and-di.md` |135| Creating DTOs, validation, pipes | `references/validation-and-dtos.md` |136| Error handling or exception filters | `references/error-handling.md` |137| Auth, authorization, security | `references/auth-and-security.md` |138| Database, ORM, queries | `references/database.md` |139| Environment config, logging, health checks | `references/config-and-logging.md` |140| Writing or improving tests | `references/testing.md` |141| CQRS, microservices, WebSockets, GraphQL, queues, caching | `references/advanced-patterns.md` |142| Dockerizing, deploying, performance | `references/deployment.md` |143144## Essential Code Patterns145146### Correct main.ts bootstrap147148```typescript149import { NestFactory } from '@nestjs/core';150import { ValidationPipe, VersioningType } from '@nestjs/common';151import { AppModule } from './app.module';152153async function bootstrap() {154 const app = await NestFactory.create(AppModule, {155 bufferLogs: true,156 });157158 app.useGlobalPipes(159 new ValidationPipe({160 whitelist: true,161 forbidNonWhitelisted: true,162 transform: true,163 transformOptions: { enableImplicitConversion: true },164 }),165 );166167 app.enableVersioning({ type: VersioningType.URI, defaultVersion: '1' });168 app.enableCors({ origin: process.env.ALLOWED_ORIGINS?.split(',') });169 app.enableShutdownHooks();170171 await app.listen(process.env.PORT ?? 3000);172}173bootstrap();174```175176### Correct module structure177178```typescript179// users/users.module.ts180@Module({181 imports: [TypeOrmModule.forFeature([User])],182 controllers: [UsersController],183 providers: [UsersService],184 exports: [UsersService], // only export what other modules need185})186export class UsersModule {}187```188189### Correct controller pattern (thin)190191```typescript192@Controller('users')193export class UsersController {194 constructor(private readonly usersService: UsersService) {}195196 @Post()197 @HttpCode(HttpStatus.CREATED)198 create(@Body() dto: CreateUserDto) {199 return this.usersService.create(dto);200 }201202 @Get(':id')203 findOne(@Param('id', ParseUUIDPipe) id: string) {204 return this.usersService.findOneOrFail(id);205 }206}207```208209### Naming conventions210211| Element | Convention | Example |212|---|---|---|213| Files | `kebab-case.<type>.ts` | `create-user.dto.ts` |214| Classes | `PascalCase` + type suffix | `CreateUserDto`, `AuthGuard` |215| Modules | `<Feature>Module` | `UsersModule` |216| Services | `<Feature>Service` | `UsersService` |217| Controllers | `<Feature>Controller` | `UsersController` |218| Entities | singular `PascalCase` | `User`, `OrderItem` |219| Test files | `*.spec.ts` (unit), `*.e2e-spec.ts` (E2E) | `users.service.spec.ts` |220221## NestJS Request Lifecycle222223```224Request → Middleware → Guards → Interceptors (pre) → Pipes → Handler → Interceptors (post) → Filters (on error)225```226227Use this to decide where logic belongs:228- **Middleware**: Logging, CORS, request ID — no access to handler context.229- **Guards**: Auth, RBAC — have `ExecutionContext`, block before interceptors.230- **Interceptors**: Response transform, timing, caching — wrap handler with RxJS.231- **Pipes**: Per-parameter validation and transformation.232- **Filters**: Error formatting — catch exceptions from any layer above.233234---235> Source: [BonsaiSoftware/bonsaipowers](https://github.com/BonsaiSoftware/bonsaipowers) — distributed by [TomeVault](https://tomevault.io).236<!-- tomevault:4.0:skill_md:2026-06-16 -->