/nestjs-patterns
NestJS architecture patterns for modules, controllers, providers, DTO validation, guards, interceptors, config, and production-grade TypeScript backends.
$ npx -y skills add affaan-m/ECC --skill nestjs-patterns --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/nestjs-patterns
Context preview
The summary Claude sees to decide when to auto-load this skill.
NestJS architecture patterns for modules, controllers, providers, DTO validation, guards, interceptors, config, and production-grade TypeScript backends.
SKILL.md
nestjs-patterns.SKILL.mdname: 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 `whitelist` and `forbidNonWhitelisted` on 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 f
Read more
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 `whitelist` and `forbidNonWhitelisted` on 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 f
Your agent can write code, but ECC gives it a coordinated engineering system and toolbox: it plans before it builds, verifies changes with tests, reviews its own work from a fresh context, remembers what matters, and turns repeated wins into reusable skills
Repo: affaan-m/ECC
Other skills on ecc.
- /everything-claude-code
Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.
Open skill - /accessibility
Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA
Open skill - /agent-architecture-audit
Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for
Open skill - /agent-eval
Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics
Open skill - /agent-harness-construction
Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates.
Open skill - /agent-introspection-debugging
Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports.
Open skill

