NestJS Patterns
SkillAI & modelsNestJS backend framework - modules, controllers, services, DI, guards, pipes, interceptors, exception filters, middleware, DTOs with class-validator
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the NestJS Patterns skill
What this skill tells your AI
The instructions your AI receives, as published by agents-inc/skills in src/skills/api-framework-nestjs/SKILL.md and read by ahel’s review.
Quick Guide: NestJS is an opinionated, modular Node.js framework built on TypeScript. Use modules to organize features, controllers for HTTP routing, services for business logic with dependency injection, DTOs with class-validator for validation, guards for auth, and exception filters for error handling. Key gotchas: always register services in module
providers, always enableValidationPipeglobally withwhitelist: true, never put business logic in controllers, never instantiate services withnew. NestJS 11 is the current stable version (opt-in SWC compiler, Express v5, reversed termination hooks).
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST use @Injectable() on every service and register it in the module providers array)
(You MUST enable ValidationPipe globally with whitelist: true and forbidNonWhitelisted: true)
(You MUST use DTOs with class-validator decorators for ALL request body validation — never validate manually in controllers)
(You MUST throw NestJS built-in HTTP exceptions (NotFoundException, BadRequestException, etc.) — never send raw status codes)
(You MUST use constructor injection for dependencies — never instantiate services manually with new)
</critical_requirements>
Auto-detection: NestJS, @nestjs/common, @nestjs/core, @Module, @Controller, @Injectable, @Get, @Post, @Body, @Param, @Query, @UseGuards, @UseInterceptors, @UsePipes, @UseFilters, CanActivate, NestInterceptor, PipeTransform, ExceptionFilter, ValidationPipe, class-validator, class-transformer
When to use:
- Building structured backend APIs with TypeScript and dependency injection
- Applications requiring modular architecture with clear separation of concerns
- REST APIs with declarative validation, authentication, and role-based access
- Projects needing the guard/interceptor/pipe/filter request lifecycle
When NOT to use:
- Simple scripts or serverless functions that don't need a framework
- Projects where Express/Fastify alone is sufficient (no DI, no modules needed)
- Frontend code
Detailed Resources:
- examples/core.md — Feature modules, CRUD, DTOs, dynamic modules, exception filters, custom providers
- examples/database.md — NestJS DI patterns for database integration, transactions
- examples/auth.md — Passport.js integration, JWT strategy, auth guards, RBAC
- examples/testing.md — Unit testing with
Test.createTestingModule, e2e with supertest - examples/advanced.md — Interceptors, custom pipes, custom decorators, config, CQRS, Swagger
- reference.md — CLI commands, project structure, decorator tables, decision frameworks
Philosophy
NestJS enforces a modular, decorator-driven architecture inspired by Angular. Every feature is organized into modules containing controllers (HTTP layer), services (business logic), and supporting infrastructure (guards, pipes, interceptors, filters).
Core principles:
- Modularity — Group related controllers, services, and providers into feature modules. Modules are the primary organizational unit.
- Dependency injection — Never instantiate services manually. Declare them as
@Injectable()and let NestJS resolve the dependency graph via constructor injection. - Decorator-driven — Decorators (
@Controller,@Get,@Body,@UseGuards) attach metadata that NestJS uses to build routing, validation, and middleware pipelines. - Separation of concerns — Controllers handle HTTP request/response. Services handle business logic. Guards handle authorization. Pipes handle validation/transformation. Filters handle exceptions.
- Convention over configuration — Follow NestJS conventions (one module per feature, one controller per resource, DTOs for validation) to get batteries-included functionality.
Key Patterns
Module System
Every NestJS app has a root AppModule that imports feature modules. Each feature module groups its controller, service, and providers. Export services that other modules need.
// Feature module — one per resource
@Module({
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService], // Available to other modules
})
export class UsersModule {}
Why good: Encapsulation per feature, explicit dependency graph via imports/exports, testable in isolation
See examples/core.md for complete CRUD module, dynamic modules, and custom providers.
Controllers — Thin Routing Layer
Controllers should only extract request data and delegate to services. No business logic.
@Controller("users")
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get(":id")
findOne(@Param("id", ParseIntPipe) id: number) {
return this.usersService.findOne(id);
}
@Post()
@HttpCode(HttpStatus.CREATED)
create(@Body() dto: CreateUserDto) {
return this.usersService.create(dto);
}
}
Why good: ParseIntPipe validates and converts param, @HttpCode for explicit status, thin delegation to service
Anti-pattern: Business logic, manual validation, or database access in controllers — always delegate to services.
DTOs with class-validator
Use DTOs with class-validator decorators for all request validation. Enable ValidationPipe globally.
const MIN_PASSWORD_LENGTH = 8;
export class CreateUserDto {
@IsEmail()
email: string;
@IsString()
@MinLength(MIN_PASSWORD_LENGTH)
password: string;
}
// Update DTO — reuses validation rules
export class UpdateUserDto extends PartialType(CreateUserDto) {}
// main.ts — Enable globally
app.useGlobalPipes(
new ValidationPipe({
whitelist: true, // Strip unknown properties
forbidNonWhitelisted: true, // Reject unknown properties
transform: true, // Auto-transform to DTO instances
}),
);
Why good: Declarative validation, whitelist prevents mass-assignment, PartialType avoids duplicating rules
See examples/core.md for nested DTOs, query DTOs with pagination, and validation groups.
Services and Dependency Injection
Services contain business logic. Decorate with @Injectable() and inject via constructor.
@Injectable()
export class UsersService {
findOne(id: number): User {
const user = this.users.find((u) => u.id === id);
if (!user) {
throw new NotFoundException(`User with id ${id} not found`);
}
return user;
}
}
Why good: @Injectable() enables DI, throws NestJS HTTP exceptions, pure business logic with no HTTP concerns
Custom Providers
Use token-based injection for non-class providers (factory, value, class providers):
const DATABASE_CONNECTION = "DATABASE_CONNECTION";
const databaseProvider = {
provide: DATABASE_CONNECTION,
useFactory: async (configService: ConfigService) => {
return createConnection(configService.get("database"));
},
inject: [ConfigService],
};
// Inject with @Inject token
constructor(@Inject(DATABASE_CONNECTION) private readonly db: Connection) {}
See examples/core.md for complete provider examples.
Exception Handling
Throw NestJS built-in HTTP exceptions from services. Use exception filters for custom error response formatting.
// Service — throw built-in exceptions
throw new NotFoundException("Resource not found");
throw new ConflictException("Resource already exists");
throw new BadRequestException("Invalid input");
throw new UnauthorizedException("Authentication required");
Key point: NestJS auto-converts these to proper HTTP responses with correct status codes. Never send raw status codes.
For custom error response shapes, use a global @Catch() exception filter. See examples/core.md.
Guards and Middleware
Guards decide whether a request proceeds (authorization). Middleware runs before routing (logging, CORS).
// Guard — implements CanActivate
@Injectable()
export class JwtAuthGuard implements CanActivate {
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest<Request>();
// Validate token, attach user to request
return true;
}
}
// Apply to routes
@UseGuards(JwtAuthGuard, RolesGuard)
@Controller("admin")
export class AdminController {}
Why good: Guards are injectable (can use services), composable (run in order), use Reflector for metadata-driven access control
See examples/auth.md for JWT auth, Passport.js integration, RBAC, and @Public() decorator.
Interceptors
Interceptors wrap handler execution for cross-cutting concerns (response wrapping, logging, caching).
@Injectable()
export class TransformResponseInterceptor<T> implements NestInterceptor {
intercept(
context: ExecutionContext,
next: CallHandler,
): Observable<ApiResponse<T>> {
return next.handle().pipe(
map((data) => ({
success: true,
data,
timestamp: new Date().toISOString(),
})),
);
}
}
See examples/advanced.md for logging, caching, and custom pipe patterns.
<decision_framework>
Decision Framework
Request Lifecycle
Incoming Request
→ Middleware (logging, CORS, body parsing)
→ Guards (authentication, authorization)
→ Interceptors (pre-handler: transform request, start timing)
→ Pipes (validation, transformation)
→ Route Handler (controller method)
→ Interceptors (post-handler: transform response, log timing)
→ Exception Filters (catch and format errors)
→ Response
Which Layer to Use
Need to process raw request before routing?
├─ YES → Middleware (logging, CORS, rate limiting)
└─ NO → Does it decide allow/deny for a route?
├─ YES → Guard (auth, roles, permissions)
└─ NO → Does it transform/validate input data?
├─ YES → Pipe (validation, type coercion)
└─ NO → Does it wrap handler execution?
├─ YES → Interceptor (timing, caching, response mapping)
└─ NO → Does it handle errors?
├─ YES → Exception Filter
└─ NO → Put it in the service layer
Module Organization
Is this a cross-cutting concern (auth, config, logging)?
├─ YES → Global module or shared module
└─ NO → Is it a business feature (users, orders, products)?
├─ YES → Feature module (users.module.ts)
└─ NO → Is it infrastructure (database, cache, queue)?
├─ YES → Infrastructure module
└─ NO → Part of the closest feature module
</decision_framework>
<red_flags>
RED FLAGS
High Priority Issues:
- Putting business logic in controllers instead of services
- Missing
@Injectable()on services (DI fails silently at runtime) - Not enabling
ValidationPipeglobally (DTOs are not validated) - Using
anyfor request body instead of typed DTOs - Instantiating services with
newinstead of constructor injection - Throwing raw
Errorinstead of NestJS HTTP exceptions (produces 500 instead of proper status)
Medium Priority Issues:
- Not exporting services from modules (other modules can't import them)
- Importing the entire module when you only need one service
- Missing
whitelist: trueon ValidationPipe (mass-assignment vulnerability) - Using
@Res()decorator outside streaming scenarios (opts out of NestJS response handling) - Not using
PartialType/PickType/OmitTypefor update DTOs (duplicated validation)
Common Mistakes:
- Circular module dependencies — restructure with
forwardRef()or extract shared logic - Forgetting to register providers in the module — service injection fails at runtime
- Using synchronous guards for async operations — return
Promise<boolean>orObservable<boolean> - Not handling all exception types in custom filters — always have a catch-all for unknown errors
Gotchas and Edge Cases:
@UseGuards(AuthGuard)takes a class reference, not an instance — NestJS instantiates via DIValidationPipewithtransform: trueconverts query params to their declared types automatically- Guards execute AFTER middleware but BEFORE interceptors and pipes
@Catch()with no arguments catches ALL exceptions, not just HttpExceptionIntrinsicException(NestJS 11) throws without framework auto-logging — useful for expected flow control- NestJS 11: Termination lifecycle hooks (
OnModuleDestroy,OnApplicationShutdown) now execute in reverse order - NestJS 11: Express v5 requires named wildcards (
/*splatinstead of/*) - NestJS 11: SWC is a supported opt-in compiler via
nest-cli.json("builder": "swc") — 20x faster builds than tsc - NestJS 11:
ParseDatePipeis now built-in — no need for custom date parsing pipes - Request-scoped providers (
Scope.REQUEST) affect performance — use only when needed forwardRef()should be a last resort — circular deps usually signal a design issue
</red_flags>
<critical_reminders>
CRITICAL REMINDERS
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST use @Injectable() on every service and register it in the module providers array)
(You MUST enable ValidationPipe globally with whitelist: true and forbidNonWhitelisted: true)
(You MUST use DTOs with class-validator decorators for ALL request body validation — never validate manually in controllers)
(You MUST throw NestJS built-in HTTP exceptions (NotFoundException, BadRequestException, etc.) — never send raw status codes)
(You MUST use constructor injection for dependencies — never instantiate services manually with new)
Failure to follow these rules will produce unvalidated, untestable NestJS code with broken dependency injection.
</critical_reminders>
Signals
- GitHub stars
- 24
- Forks
- 8
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
api-framework-nestjs- Source
- github.com/agents-inc/skills