<< All versions
Skill v1.0.1
currentAutomated scan91/100affaan-m/ecc/nestjs-patterns
+4 new
──Details
PublishedJune 28, 2026 at 06:49 PM
Content Hashsha256:984df44b40cdb5ac...
Git SHA2bc924faf2f8
Bump Typepatch
──Files
Files (1 file, 6.3 KB)
SKILL.md6.3 KBactive
SKILL.md · 233 lines · 6.3 KB
version: "1.0.1" 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
text
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
ts
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
ts
@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
ts
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
ts
@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
ts
@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
ts
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
ts
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.