PluginBench
Skill
Pass
Audit score 90

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
Prerequisites
  • 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)
Claude Code
Cursor
Windsurf
Cline

How to use nestjs-patterns

  1. 1.Review the project structure section and organize your src/ folder with modules, common/, and config/ directories
  2. 2.Set up global validation pipe and exception filter in main.ts using the bootstrap pattern shown
  3. 3.Create feature modules with controllers, services, and DTOs following the thin-controller pattern
  4. 4.Define request DTOs with class-validator decorators (@IsEmail, @IsString, etc.) for automatic validation
  5. 5.Implement guards and strategies in module-local auth/ folders, then apply with @UseGuards() decorators
  6. 6.Configure environment validation at boot using ConfigModule.forRoot() with a validate function
  7. 7.Write unit tests for services with mocked dependencies and integration tests for HTTP endpoints using Test.createTestingModule()

Use cases

Good for
  • 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
Who it's for
  • 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

Should I put all guards and filters in common/ or keep them module-local?

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/.

How do I avoid leaking sensitive fields like password hashes in API responses?

Use dedicated response DTOs or serializers instead of returning ORM entities directly. The ClassSerializerInterceptor with @Exclude() decorators helps strip internal fields automatically.

When should I use a global validation pipe vs. per-route validation?

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.

How do I structure multi-step writes that span multiple tables or services?

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.

What is the recommended way to handle environment-specific config?

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 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 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.