"NestJS modular architecture with dependency injection. Trigger: When building scalable server apps with NestJS."
Install
npx skillscat add joabgonzalez/ai-agents-skills/nest Install via the SkillsCat registry.
SKILL.md
NestJS Skill
Scalable server apps with NestJS modules, dependency injection, and decorators.
When to Use
- Modular server apps
- Dependency injection
- Scalable APIs
Don't use for:
- Single-file scripts/CLIs (use Node.js)
- Lightweight edge functions (use Hono/Express)
- Frontend-only projects
Critical Patterns
✅ REQUIRED: Module / Controller / Service Structure
Each feature in own module with controllers and providers.
// CORRECT: one module encapsulates the feature
@Module({
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
// WRONG: registering all services in AppModule✅ REQUIRED: Dependency Injection
Inject via constructor; never instantiate manually.
// CORRECT: let the DI container manage instances
@Controller("users")
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get(":id")
findOne(@Param("id") id: string) {
return this.usersService.findOne(id);
}
}
// WRONG: const service = new UsersService()✅ REQUIRED: Guards, Pipes, and Interceptors
Built-in lifecycle hooks for cross-cutting concerns.
// CORRECT: guard for auth, pipe for validation, interceptor for transform
@UseGuards(AuthGuard)
@UsePipes(new ValidationPipe({ whitelist: true }))
@UseInterceptors(ClassSerializerInterceptor)
@Controller("users")
export class UsersController {}✅ REQUIRED: DTOs with class-validator
DTOs with decorators for auto-validation.
export class CreateUserDto {
@IsString() @MinLength(2) name: string;
@IsEmail() email: string;
@IsOptional() @IsString() bio?: string;
}
@Post()
create(@Body() dto: CreateUserDto) {
return this.usersService.create(dto);
}✅ REQUIRED: Exception Filters
Map domain errors to HTTP responses centrally.
@Catch(DomainException)
export class DomainExceptionFilter implements ExceptionFilter {
catch(exception: DomainException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
ctx.getResponse().status(exception.status).json({
error: exception.message,
});
}
}Decision Tree
- REST API? -> Use
@Controllerwith HTTP method decorators - GraphQL API? -> Use
@Resolverwith@nestjs/graphql - Shared business logic? -> Extract into an
@Injectable()service - Request validation? -> Apply
ValidationPipeglobally or per-route - Auth check? -> Implement a Guard with
canActivate - Response shaping? -> Use an Interceptor or Serializer
- Background work? -> Use
@nestjs/bullor@nestjs/schedule - Config secrets? -> Use
ConfigModule.forRoot()with.env
Example
@Controller("users")
export class UsersController {
constructor(private readonly users: UsersService) {}
@Get()
findAll() { return this.users.findAll(); }
@Post()
@UsePipes(new ValidationPipe({ whitelist: true }))
create(@Body() dto: CreateUserDto) { return this.users.create(dto); }
@Get(":id")
async findOne(@Param("id", ParseIntPipe) id: number) {
const user = await this.users.findOne(id);
if (!user) throw new NotFoundException("User not found");
return user;
}
}Edge Cases
- Circular deps: Use
forwardRef(() => SomeModule)for mutual dependencies. - Request-scoped providers: Default singleton;
Scope.REQUESThurts performance. - Module order: Import
ConfigModulebefore dependent modules. - Global pipes:
app.useGlobalPipes()skips DI; useAPP_PIPEprovider for injected deps. - Test mocks: Override providers in
Test.createTestingModule(), not imports.
Checklist
- Each feature has its own module with controllers and providers
- Services are injected via constructors, never manually instantiated
-
ValidationPipewithwhitelist: trueis applied globally or per-route - DTOs use class-validator decorators for all input
- Guards protect authenticated routes
- Exception filters map domain errors to HTTP status codes
-
ConfigModuleloads environment variables before dependent modules - Unit tests mock providers via the testing module