nestjs-patterns
affaan-m/ecc
Production-grade NestJS patterns for modular TypeScript backends with validation, guards, and config.
What is nestjs-patterns?
A reference guide for building scalable NestJS APIs using proven architectural patterns. Covers module structure, controllers, providers, DTO validation, guards, interceptors, exception handling, and environment configuration. Use this when designing or refactoring a NestJS backend for production readiness.
- Provides modular project structure with feature modules, controllers, services, and cross-cutting concerns
- Demonstrates global validation pipes with whitelist enforcement and DTO validation using class-validator
- Shows auth patterns including JWT guards, role-based access control, and request context handling
- Covers exception filters and consistent error response envelopes across APIs
- Includes environment validation at boot time and typed config service patterns
- Offers testing patterns for unit tests, integration tests, and HTTP endpoint validation
How to install nestjs-patterns
npx skills add null --skill nestjs-patterns- Node.js and npm/yarn installed
- Basic familiarity with TypeScript and decorators
- NestJS CLI installed (npm i -g @nestjs/cli)
- Understanding of HTTP concepts (controllers, routes, middleware)
How to use nestjs-patterns
- 1.Review the project structure section and organize your src/ folder with modules, common/, and config/ directories
- 2.Set up global validation pipe and exception filter in main.ts using the bootstrap pattern shown
- 3.Create feature modules with controllers, services, and DTOs following the thin-controller pattern
- 4.Define request DTOs with class-validator decorators (@IsEmail, @IsString, etc.) for automatic validation
- 5.Implement guards and strategies in module-local auth/ folders, then apply with @UseGuards() decorators
- 6.Configure environment validation at boot using ConfigModule.forRoot() with a validate function
- 7.Write unit tests for services with mocked dependencies and integration tests for HTTP endpoints using Test.createTestingModule()
Use cases
- Building a REST API with multiple feature modules (auth, users, products) that share common guards and validation
- Setting up a NestJS microservice with environment-aware config and health checks for database connections
- Implementing role-based access control with custom guards and decorators for admin-only endpoints
- Structuring a backend with Prisma or TypeORM repositories isolated behind domain-language services
- Adding global exception handling and structured logging with request correlation IDs for production deployments
- Backend engineers building NestJS APIs or microservices
- Teams standardizing on TypeScript and dependency injection patterns
- Developers transitioning from Express or other frameworks to NestJS
- DevOps and platform engineers setting up production-grade Node.js services
nestjs-patterns FAQ
Keep auth strategies and guards module-local unless they are truly shared across multiple modules. Only cross-cutting concerns like global exception filters and validation pipes belong in common/.
Use dedicated response DTOs or serializers instead of returning ORM entities directly. The ClassSerializerInterceptor with @Exclude() decorators helps strip internal fields automatically.
Always use one global ValidationPipe in bootstrap() with whitelist and forbidNonWhitelisted enabled. This avoids repeating config per route and ensures consistent validation across all endpoints.
Isolate transactional workflows in services that own the unit of work. Keep repository/ORM code behind providers, and do not let controllers coordinate multi-step writes directly.
Validate env at boot time using ConfigModule.forRoot() with a validate function. Split dev/staging/prod concerns in config factories instead of branching throughout feature code.
Full instructions (SKILL.md)
Source of truth, from affaan-m/ecc.
name: nestjs-patterns description: NestJS architecture patterns for modules, controllers, providers, DTO validation, guards, interceptors, config, and production-grade TypeScript backends. metadata: origin: ECC
NestJS Development Patterns
Production-grade NestJS patterns for modular TypeScript backends.
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
Project Structure
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.
Bootstrap and Global Validation
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
whitelistandforbidNonWhitelistedon public APIs. - Prefer one global validation pipe instead of repeating validation config per route.
Modules, Controllers, and Providers
@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.
DTOs and Validation
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.
Auth, Guards, and Request Context
@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.
Exception Filters and Error Shape
@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.
Config and Environment Validation
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.
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.
Testing
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.
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.
Related skills
More from affaan-m/ecc and the wider catalog.
netmiko-ssh-automation
Safe Python Netmiko patterns for read-only SSH collection, bounded batch automation, and guarded config changes.
network-bgp-diagnostics
Read-only BGP troubleshooting: diagnose neighbor state, route exchange, and policy issues safely.
network-config-validation
Pre-deployment validation for Cisco IOS/IOS-XE configs: dangerous commands, IP conflicts, stale references, and security hygiene.
network-interface-health
Diagnose physical link, duplex, and congestion issues by analyzing interface error and drop counters on routers, switches, and Linux hosts.
nextjs-turbopack
Next.js 16+ incremental bundler with Rust-powered Turbopack for 5–14x faster dev startup and HMR.
nodejs-keccak256
Use Ethereum's Keccak-256, not Node's NIST SHA3, to prevent silent hashing bugs in selectors, signatures, and addresses.