Claude Skill

api-framework-nestjs

NestJS backend framework - modules, controllers, services, DI, guards, pipes, interceptors, exception filters, middleware, DTOs with class-validator

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download agents-inc-skills-dist_plugins_api-framework-nestjs_skills_api-framework-nestjs-3a51ef5.zip · 28 KB
Part of agents-inc/skills — 130 skills

Install

skills CLI npx skills add https://github.com/agents-inc/skills/tree/main/dist/plugins/api-framework-nestjs/skills/api-framework-nestjs
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install agents-inc-skills@llmmart
Git git clone https://github.com/agents-inc/skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole agents-inc/skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

NestJS Patterns

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 enable ValidationPipe globally with whitelist: true, never put business logic in controllers, never instantiate services with new. 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



<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 ValidationPipe globally (DTOs are not validated)
  • Using any for request body instead of typed DTOs
  • Instantiating services with new instead of constructor injection
  • Throwing raw Error instead 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: true on ValidationPipe (mass-assignment vulnerability)
  • Using @Res() decorator outside streaming scenarios (opts out of NestJS response handling)
  • Not using PartialType / PickType / OmitType for 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> or Observable<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 DI
  • ValidationPipe with transform: true converts 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 HttpException
  • IntrinsicException (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 (/*splat instead of /*)
  • NestJS 11: SWC is a supported opt-in compiler via nest-cli.json ("builder": "swc") — 20x faster builds than tsc
  • NestJS 11: ParseDatePipe is 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>

Files (skills)
  • examples
    • advanced.md 15.7 KB
      # NestJS Advanced Patterns
      
      > Interceptors, pipes, custom decorators, and CQRS patterns. See [SKILL.md](../SKILL.md) for core concepts.
      
      ---
      
      ## Pattern 1: Interceptors
      
      Interceptors wrap the handler execution pipeline. They can transform the response, add logging, implement caching, or handle errors.
      
      ### Good Example — Response Wrapping Interceptor
      
      ```typescript
      // interceptors/transform-response.interceptor.ts
      import {
        Injectable,
        NestInterceptor,
        ExecutionContext,
        CallHandler,
      } from "@nestjs/common";
      import { Observable } from "rxjs";
      import { map } from "rxjs/operators";
      
      interface ApiResponse<T> {
        success: boolean;
        data: T;
        timestamp: string;
      }
      
      @Injectable()
      export class TransformResponseInterceptor<T> implements NestInterceptor<
        T,
        ApiResponse<T>
      > {
        intercept(
          context: ExecutionContext,
          next: CallHandler<T>,
        ): Observable<ApiResponse<T>> {
          return next.handle().pipe(
            map((data) => ({
              success: true,
              data,
              timestamp: new Date().toISOString(),
            })),
          );
        }
      }
      ```
      
      **Why good:** Wraps all responses in a consistent envelope, uses RxJS `map` operator, typed with generics, implements `NestInterceptor` interface properly
      
      ### Good Example — Logging and Timing Interceptor
      
      ```typescript
      // interceptors/logging.interceptor.ts
      import {
        Injectable,
        NestInterceptor,
        ExecutionContext,
        CallHandler,
        Logger,
      } from "@nestjs/common";
      import { Observable } from "rxjs";
      import { tap } from "rxjs/operators";
      import type { Request } from "express";
      
      const SLOW_REQUEST_THRESHOLD_MS = 3000;
      
      @Injectable()
      export class LoggingInterceptor implements NestInterceptor {
        private readonly logger = new Logger(LoggingInterceptor.name);
      
        intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
          const request = context.switchToHttp().getRequest<Request>();
          const { method, url } = request;
          const now = Date.now();
      
          return next.handle().pipe(
            tap(() => {
              const duration = Date.now() - now;
              const logMessage = `${method} ${url} — ${duration}ms`;
      
              if (duration > SLOW_REQUEST_THRESHOLD_MS) {
                this.logger.warn(`SLOW: ${logMessage}`);
              } else {
                this.logger.log(logMessage);
              }
            }),
          );
        }
      }
      ```
      
      **Why good:** Named constant for slow threshold, warns on slow requests, uses `tap` (side effect without transforming data), scoped Logger
      
      ### Good Example — Cache Interceptor
      
      ```typescript
      // interceptors/cache.interceptor.ts
      import {
        Injectable,
        NestInterceptor,
        ExecutionContext,
        CallHandler,
      } from "@nestjs/common";
      import { Observable, of } from "rxjs";
      import { tap } from "rxjs/operators";
      import type { Request } from "express";
      
      const CACHE_TTL_MS = 60_000; // 1 minute
      
      interface CacheEntry {
        data: unknown;
        expiry: number;
      }
      
      @Injectable()
      export class SimpleCacheInterceptor implements NestInterceptor {
        private readonly cache = new Map<string, CacheEntry>();
      
        intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
          const request = context.switchToHttp().getRequest<Request>();
      
          // Only cache GET requests
          if (request.method !== "GET") {
            return next.handle();
          }
      
          const cacheKey = request.url;
          const cached = this.cache.get(cacheKey);
      
          if (cached && cached.expiry > Date.now()) {
            return of(cached.data);
          }
      
          return next.handle().pipe(
            tap((data) => {
              this.cache.set(cacheKey, {
                data,
                expiry: Date.now() + CACHE_TTL_MS,
              });
            }),
          );
        }
      }
      ```
      
      **Why good:** Only caches GET requests, TTL-based expiry, `of()` returns cached value as Observable, named constant for TTL
      
      ### Applying Interceptors
      
      ```typescript
      // Apply to a single route
      @UseInterceptors(LoggingInterceptor)
      @Get()
      findAll() { /* ... */ }
      
      // Apply to all routes in a controller
      @UseInterceptors(LoggingInterceptor)
      @Controller('users')
      export class UsersController { /* ... */ }
      
      // Apply globally in main.ts
      app.useGlobalInterceptors(new TransformResponseInterceptor());
      
      // Apply globally with DI (can inject dependencies)
      @Module({
        providers: [
          { provide: APP_INTERCEPTOR, useClass: LoggingInterceptor },
        ],
      })
      export class AppModule {}
      ```
      
      ---
      
      ## Pattern 2: Custom Pipes
      
      Pipes transform and validate input data before it reaches the handler.
      
      ### Good Example — Using Built-in ParseDatePipe (NestJS 11+)
      
      ```typescript
      // NestJS 11 includes ParseDatePipe — no custom pipe needed
      import { ParseDatePipe } from '@nestjs/common';
      
      @Get('events')
      findByDate(@Query('date', ParseDatePipe) date: Date) {
        return this.eventsService.findByDate(date);
      }
      ```
      
      **Why good:** Built-in pipe handles validation and transformation, throws `BadRequestException` for invalid dates, no custom code needed
      
      ### Good Example — Custom Pipe (for domain-specific validation)
      
      ```typescript
      // pipes/parse-positive-int.pipe.ts
      import { PipeTransform, Injectable, BadRequestException } from '@nestjs/common';
      
      @Injectable()
      export class ParsePositiveIntPipe implements PipeTransform<string, number> {
        transform(value: string): number {
          const num = parseInt(value, 10);
      
          if (isNaN(num) || num <= 0) {
            throw new BadRequestException(
              `"${value}" is not a valid positive integer.`,
            );
          }
      
          return num;
        }
      }
      
      // Usage
      @Get('items')
      findByPage(@Query('page', ParsePositiveIntPipe) page: number) {
        return this.itemsService.findPage(page);
      }
      ```
      
      **Why good:** Custom pipe for domain validation beyond built-ins, typed input/output with `PipeTransform<string, number>`, descriptive error
      
      ### Good Example — Enum Validation Pipe
      
      ```typescript
      // pipes/parse-enum.pipe.ts
      import { PipeTransform, Injectable, BadRequestException } from '@nestjs/common';
      
      @Injectable()
      export class ParseEnumPipe<T extends Record<string, string>>
        implements PipeTransform<string, string>
      {
        constructor(private readonly enumType: T) {}
      
        transform(value: string): string {
          const enumValues = Object.values(this.enumType);
      
          if (!enumValues.includes(value)) {
            throw new BadRequestException(
              `"${value}" is not valid. Expected one of: ${enumValues.join(', ')}`,
            );
          }
      
          return value;
        }
      }
      
      // Usage
      enum OrderStatus {
        Pending = 'pending',
        Processing = 'processing',
        Shipped = 'shipped',
        Delivered = 'delivered',
      }
      
      @Get()
      findByStatus(
        @Query('status', new ParseEnumPipe(OrderStatus)) status: string,
      ) {
        return this.ordersService.findByStatus(status);
      }
      ```
      
      **Why good:** Generic enum validation, descriptive error message lists valid values, reusable across different enums
      
      ---
      
      ## Pattern 3: Custom Decorators
      
      ### Good Example — Combined Decorators
      
      ```typescript
      // decorators/api-paginated.decorator.ts
      import { applyDecorators, Get, UseInterceptors } from '@nestjs/common';
      import { TransformResponseInterceptor } from '../interceptors/transform-response.interceptor';
      
      export function ApiPaginated(path?: string) {
        return applyDecorators(
          Get(path),
          UseInterceptors(TransformResponseInterceptor),
        );
      }
      
      // Usage — replaces @Get() + @UseInterceptors(...)
      @ApiPaginated()
      findAll(@Query() query: QueryProductsDto) {
        return this.productsService.findAll(query);
      }
      ```
      
      **Why good:** `applyDecorators` composes multiple decorators, reduces boilerplate on controller methods, single source of truth for endpoint configuration
      
      ### Good Example — Param Decorator with Validation
      
      ```typescript
      // decorators/user-agent.decorator.ts
      import { createParamDecorator, ExecutionContext } from '@nestjs/common';
      import type { Request } from 'express';
      
      export const UserAgent = createParamDecorator(
        (_data: unknown, ctx: ExecutionContext): string => {
          const request = ctx.switchToHttp().getRequest<Request>();
          return request.headers['user-agent'] ?? 'unknown';
        },
      );
      
      // Usage
      @Get()
      findAll(@UserAgent() userAgent: string) {
        this.logger.log(`Request from: ${userAgent}`);
        return this.productsService.findAll();
      }
      ```
      
      ### Good Example — Method Decorator for Metadata
      
      ```typescript
      // decorators/cache-ttl.decorator.ts
      import { SetMetadata } from '@nestjs/common';
      
      const CACHE_TTL_KEY = 'cacheTtl';
      const DEFAULT_CACHE_TTL_MS = 60_000;
      
      export const CacheTtl = (ttlMs: number = DEFAULT_CACHE_TTL_MS) =>
        SetMetadata(CACHE_TTL_KEY, ttlMs);
      
      // Usage
      @Get('popular')
      @CacheTtl(300_000) // 5 minutes
      getPopularProducts() {
        return this.productsService.getPopular();
      }
      ```
      
      **Why good:** Named constant for default TTL and metadata key, `SetMetadata` stores value for interceptor/guard to read via `Reflector`, clean API
      
      ---
      
      ## Pattern 4: Config Module Patterns
      
      ### Good Example — Typed Configuration
      
      ```typescript
      // config/database.config.ts
      import { registerAs } from "@nestjs/config";
      
      export const databaseConfig = registerAs("database", () => ({
        host: process.env.DB_HOST ?? "localhost",
        port: parseInt(process.env.DB_PORT ?? "5432", 10),
        username: process.env.DB_USER ?? "postgres",
        password: process.env.DB_PASSWORD ?? "",
        name: process.env.DB_NAME ?? "app",
      }));
      ```
      
      ```typescript
      // config/app.config.ts
      import { registerAs } from "@nestjs/config";
      
      const DEFAULT_PORT = 3000;
      
      export const appConfig = registerAs("app", () => ({
        port: parseInt(process.env.PORT ?? String(DEFAULT_PORT), 10),
        nodeEnv: process.env.NODE_ENV ?? "development",
        apiPrefix: process.env.API_PREFIX ?? "api/v1",
      }));
      ```
      
      ```typescript
      // app.module.ts
      import { ConfigModule } from "@nestjs/config";
      import { databaseConfig } from "./config/database.config";
      import { appConfig } from "./config/app.config";
      
      @Module({
        imports: [
          ConfigModule.forRoot({
            isGlobal: true,
            load: [databaseConfig, appConfig],
          }),
        ],
      })
      export class AppModule {}
      ```
      
      ```typescript
      // Usage with injection
      import { Inject, Injectable } from "@nestjs/common";
      import { ConfigType } from "@nestjs/config";
      import { databaseConfig } from "../config/database.config";
      
      @Injectable()
      export class DatabaseService {
        constructor(
          @Inject(databaseConfig.KEY)
          private readonly dbConfig: ConfigType<typeof databaseConfig>,
        ) {
          // Fully typed: this.dbConfig.host, this.dbConfig.port, etc.
        }
      }
      ```
      
      **Why good:** `registerAs` creates namespaced config, `ConfigType` provides full type inference, `isGlobal: true` avoids importing ConfigModule everywhere, defaults for development
      
      ---
      
      ## Pattern 5: CQRS Pattern
      
      For complex domains, separate read and write operations.
      
      ### Command
      
      ```typescript
      // orders/commands/create-order.command.ts
      export class CreateOrderCommand {
        constructor(
          public readonly userId: number,
          public readonly items: Array<{ productId: number; quantity: number }>,
        ) {}
      }
      ```
      
      ### Command Handler
      
      ```typescript
      // orders/commands/handlers/create-order.handler.ts
      import { CommandHandler, ICommandHandler, EventBus } from "@nestjs/cqrs";
      import { CreateOrderCommand } from "../create-order.command";
      import { OrderCreatedEvent } from "../../events/order-created.event";
      
      @CommandHandler(CreateOrderCommand)
      export class CreateOrderHandler implements ICommandHandler<CreateOrderCommand> {
        constructor(
          private readonly ordersRepository: OrdersRepository,
          private readonly eventBus: EventBus,
        ) {}
      
        async execute(command: CreateOrderCommand) {
          const order = await this.ordersRepository.create({
            userId: command.userId,
            items: command.items,
            status: "pending",
          });
      
          this.eventBus.publish(new OrderCreatedEvent(order.id, command.userId));
      
          return order;
        }
      }
      ```
      
      ### Event and Event Handler
      
      ```typescript
      // orders/events/order-created.event.ts
      export class OrderCreatedEvent {
        constructor(
          public readonly orderId: number,
          public readonly userId: number,
        ) {}
      }
      ```
      
      ```typescript
      // orders/events/handlers/order-created.handler.ts
      import { EventsHandler, IEventHandler } from "@nestjs/cqrs";
      import { OrderCreatedEvent } from "../order-created.event";
      import { NotificationsService } from "../../../notifications/notifications.service";
      
      @EventsHandler(OrderCreatedEvent)
      export class OrderCreatedHandler implements IEventHandler<OrderCreatedEvent> {
        constructor(private readonly notificationsService: NotificationsService) {}
      
        async handle(event: OrderCreatedEvent) {
          await this.notificationsService.sendOrderConfirmation(
            event.userId,
            event.orderId,
          );
        }
      }
      ```
      
      ### Controller Using CQRS
      
      ```typescript
      // orders/orders.controller.ts
      import { Controller, Post, Body } from "@nestjs/common";
      import { CommandBus, QueryBus } from "@nestjs/cqrs";
      import { CreateOrderCommand } from "./commands/create-order.command";
      import { GetOrderQuery } from "./queries/get-order.query";
      import { CurrentUser } from "../auth/decorators/current-user.decorator";
      
      @Controller("orders")
      export class OrdersController {
        constructor(
          private readonly commandBus: CommandBus,
          private readonly queryBus: QueryBus,
        ) {}
      
        @Post()
        createOrder(@CurrentUser("id") userId: number, @Body() dto: CreateOrderDto) {
          return this.commandBus.execute(new CreateOrderCommand(userId, dto.items));
        }
      
        @Get(":id")
        getOrder(@Param("id", ParseIntPipe) id: number) {
          return this.queryBus.execute(new GetOrderQuery(id));
        }
      }
      ```
      
      **Why good:** Clean separation of read/write concerns, event-driven side effects (notifications), controller doesn't know about business logic details, commands and queries are plain classes (easy to test)
      
      **When to use:** Complex domains with many side effects, systems where read and write models differ significantly, event-sourced architectures
      
      **When not to use:** Simple CRUD applications (overkill), small teams (unnecessary complexity)
      
      ---
      
      ## Pattern 6: Swagger/OpenAPI Documentation
      
      ### Setup
      
      ```typescript
      // main.ts
      import { NestFactory } from "@nestjs/core";
      import { SwaggerModule, DocumentBuilder } from "@nestjs/swagger";
      import { AppModule } from "./app.module";
      
      const PORT = 3000;
      
      async function bootstrap() {
        const app = await NestFactory.create(AppModule);
      
        const config = new DocumentBuilder()
          .setTitle("My API")
          .setDescription("API documentation")
          .setVersion("1.0")
          .addBearerAuth()
          .build();
      
        const document = SwaggerModule.createDocument(app, config);
        SwaggerModule.setup("api/docs", app, document);
      
        await app.listen(PORT);
      }
      bootstrap();
      ```
      
      ### DTO with Swagger Decorators
      
      ```typescript
      // users/dto/create-user.dto.ts
      import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
      import { IsEmail, IsString, MinLength, IsOptional } from "class-validator";
      
      const MIN_PASSWORD_LENGTH = 8;
      
      export class CreateUserDto {
        @ApiProperty({ example: "alice@example.com" })
        @IsEmail()
        email: string;
      
        @ApiProperty({ minLength: MIN_PASSWORD_LENGTH })
        @IsString()
        @MinLength(MIN_PASSWORD_LENGTH)
        password: string;
      
        @ApiProperty({ example: "Alice" })
        @IsString()
        name: string;
      
        @ApiPropertyOptional({ example: "user", enum: ["user", "admin"] })
        @IsOptional()
        @IsString()
        role?: string;
      }
      ```
      
      ### Controller with Swagger Decorators
      
      ```typescript
      import {
        ApiTags,
        ApiBearerAuth,
        ApiOperation,
        ApiResponse,
      } from "@nestjs/swagger";
      
      @ApiTags("users")
      @ApiBearerAuth()
      @Controller("users")
      export class UsersController {
        @ApiOperation({ summary: "Create a new user" })
        @ApiResponse({ status: 201, description: "User created successfully" })
        @ApiResponse({ status: 400, description: "Validation failed" })
        @ApiResponse({ status: 409, description: "Email already exists" })
        @Post()
        create(@Body() dto: CreateUserDto) {
          return this.usersService.create(dto);
        }
      }
      ```
      
      **Why good:** Auto-generated API docs at `/api/docs`, `@ApiProperty` examples appear in Swagger UI, `@ApiResponse` documents all possible outcomes, `@ApiBearerAuth` adds auth to Swagger UI
      
      ---
      
      _For core patterns, see [core.md](core.md). For database patterns, see [database.md](database.md). For auth patterns, see [auth.md](auth.md). For testing, see [testing.md](testing.md)._
      
    • auth.md 10.4 KB
      # NestJS Authentication Examples
      
      > Passport.js integration, JWT strategy, and auth guards for NestJS. See [SKILL.md](../SKILL.md) for core concepts.
      
      ---
      
      ## Pattern 1: JWT Authentication with Passport
      
      ### Auth Module Setup
      
      ```typescript
      // auth/auth.module.ts
      import { Module } from "@nestjs/common";
      import { JwtModule } from "@nestjs/jwt";
      import { PassportModule } from "@nestjs/passport";
      import { ConfigModule, ConfigService } from "@nestjs/config";
      import { AuthService } from "./auth.service";
      import { AuthController } from "./auth.controller";
      import { JwtStrategy } from "./strategies/jwt.strategy";
      import { LocalStrategy } from "./strategies/local.strategy";
      import { UsersModule } from "../users/users.module";
      
      const JWT_EXPIRATION = "1h";
      
      @Module({
        imports: [
          UsersModule,
          PassportModule,
          JwtModule.registerAsync({
            imports: [ConfigModule],
            inject: [ConfigService],
            useFactory: (configService: ConfigService) => ({
              secret: configService.get<string>("JWT_SECRET"),
              signOptions: { expiresIn: JWT_EXPIRATION },
            }),
          }),
        ],
        controllers: [AuthController],
        providers: [AuthService, JwtStrategy, LocalStrategy],
        exports: [AuthService],
      })
      export class AuthModule {}
      ```
      
      **Why good:** Async JWT config reads secret from environment, named constant for expiration, imports `UsersModule` for user lookup, exports `AuthService` for use by other modules
      
      ### Auth Service
      
      ```typescript
      // auth/auth.service.ts
      import { Injectable, UnauthorizedException } from "@nestjs/common";
      import { JwtService } from "@nestjs/jwt";
      import * as bcrypt from "bcrypt";
      import { UsersService } from "../users/users.service";
      
      const BCRYPT_SALT_ROUNDS = 10;
      
      interface JwtPayload {
        sub: number;
        email: string;
        role: string;
      }
      
      interface TokenResponse {
        accessToken: string;
      }
      
      @Injectable()
      export class AuthService {
        constructor(
          private readonly usersService: UsersService,
          private readonly jwtService: JwtService,
        ) {}
      
        async validateUser(email: string, password: string) {
          const user = await this.usersService.findByEmail(email);
          if (!user) {
            throw new UnauthorizedException("Invalid credentials");
          }
      
          const isPasswordValid = await bcrypt.compare(password, user.password);
          if (!isPasswordValid) {
            throw new UnauthorizedException("Invalid credentials");
          }
      
          // Return user without password
          const { password: _password, ...result } = user;
          return result;
        }
      
        async login(user: {
          id: number;
          email: string;
          role: string;
        }): Promise<TokenResponse> {
          const payload: JwtPayload = {
            sub: user.id,
            email: user.email,
            role: user.role,
          };
      
          return {
            accessToken: this.jwtService.sign(payload),
          };
        }
      
        async hashPassword(password: string): Promise<string> {
          return bcrypt.hash(password, BCRYPT_SALT_ROUNDS);
        }
      }
      ```
      
      **Why good:** Named constant for salt rounds, generic error message ("Invalid credentials") prevents user enumeration, strips password from returned user, typed JWT payload
      
      ### Local Strategy (Username/Password)
      
      ```typescript
      // auth/strategies/local.strategy.ts
      import { Injectable } from "@nestjs/common";
      import { PassportStrategy } from "@nestjs/passport";
      import { Strategy } from "passport-local";
      import { AuthService } from "../auth.service";
      
      @Injectable()
      export class LocalStrategy extends PassportStrategy(Strategy) {
        constructor(private readonly authService: AuthService) {
          super({ usernameField: "email" }); // Use email instead of username
        }
      
        async validate(email: string, password: string) {
          return this.authService.validateUser(email, password);
        }
      }
      ```
      
      **Why good:** `usernameField: 'email'` configures Passport to use email, `validate()` delegates to AuthService, return value is attached to `request.user`
      
      ### JWT Strategy (Token Verification)
      
      ```typescript
      // auth/strategies/jwt.strategy.ts
      import { Injectable } from "@nestjs/common";
      import { PassportStrategy } from "@nestjs/passport";
      import { ExtractJwt, Strategy } from "passport-jwt";
      import { ConfigService } from "@nestjs/config";
      
      interface JwtPayload {
        sub: number;
        email: string;
        role: string;
      }
      
      @Injectable()
      export class JwtStrategy extends PassportStrategy(Strategy) {
        constructor(configService: ConfigService) {
          super({
            jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
            ignoreExpiration: false,
            secretOrKey: configService.get<string>("JWT_SECRET"),
          });
        }
      
        async validate(payload: JwtPayload) {
          // Return value is attached to request.user
          return {
            id: payload.sub,
            email: payload.email,
            role: payload.role,
          };
        }
      }
      ```
      
      **Why good:** Extracts token from `Authorization: Bearer <token>` header, secret from config (not hardcoded), `validate()` maps JWT claims to user object
      
      ### Auth Controller
      
      ```typescript
      // auth/auth.controller.ts
      import {
        Controller,
        Post,
        Body,
        UseGuards,
        Request,
        HttpCode,
        HttpStatus,
      } from "@nestjs/common";
      import { AuthGuard } from "@nestjs/passport";
      import { AuthService } from "./auth.service";
      import { LoginDto } from "./dto/login.dto";
      
      @Controller("auth")
      export class AuthController {
        constructor(private readonly authService: AuthService) {}
      
        @Post("login")
        @UseGuards(AuthGuard("local"))
        @HttpCode(HttpStatus.OK)
        async login(
          @Request() req: { user: { id: number; email: string; role: string } },
        ) {
          return this.authService.login(req.user);
        }
      }
      ```
      
      ### Login DTO
      
      ```typescript
      // auth/dto/login.dto.ts
      import { IsEmail, IsString, MinLength } from "class-validator";
      
      const MIN_PASSWORD_LENGTH = 8;
      
      export class LoginDto {
        @IsEmail()
        email: string;
      
        @IsString()
        @MinLength(MIN_PASSWORD_LENGTH)
        password: string;
      }
      ```
      
      ---
      
      ## Pattern 2: Auth Guards for Route Protection
      
      ### JWT Auth Guard
      
      ```typescript
      // auth/guards/jwt-auth.guard.ts
      import { Injectable, ExecutionContext } from "@nestjs/common";
      import { AuthGuard } from "@nestjs/passport";
      import { Reflector } from "@nestjs/core";
      
      const IS_PUBLIC_KEY = "isPublic";
      
      @Injectable()
      export class JwtAuthGuard extends AuthGuard("jwt") {
        constructor(private readonly reflector: Reflector) {
          super();
        }
      
        canActivate(context: ExecutionContext) {
          // Check for @Public() decorator
          const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
            context.getHandler(),
            context.getClass(),
          ]);
      
          if (isPublic) {
            return true;
          }
      
          return super.canActivate(context);
        }
      }
      ```
      
      ### Public Decorator
      
      ```typescript
      // auth/decorators/public.decorator.ts
      import { SetMetadata } from "@nestjs/common";
      
      const IS_PUBLIC_KEY = "isPublic";
      
      export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
      ```
      
      ### Current User Decorator
      
      ```typescript
      // auth/decorators/current-user.decorator.ts
      import { createParamDecorator, ExecutionContext } from "@nestjs/common";
      
      interface AuthUser {
        id: number;
        email: string;
        role: string;
      }
      
      export const CurrentUser = createParamDecorator(
        (data: keyof AuthUser | undefined, ctx: ExecutionContext) => {
          const request = ctx.switchToHttp().getRequest();
          const user = request.user as AuthUser;
          return data ? user[data] : user;
        },
      );
      ```
      
      ### Global Guard Registration
      
      ```typescript
      // app.module.ts
      import { Module } from "@nestjs/common";
      import { APP_GUARD } from "@nestjs/core";
      import { JwtAuthGuard } from "./auth/guards/jwt-auth.guard";
      
      @Module({
        providers: [
          {
            provide: APP_GUARD,
            useClass: JwtAuthGuard,
          },
        ],
      })
      export class AppModule {}
      ```
      
      ### Usage in Controllers
      
      ```typescript
      // users/users.controller.ts
      import { Controller, Get, Param, ParseIntPipe } from "@nestjs/common";
      import { Public } from "../auth/decorators/public.decorator";
      import { CurrentUser } from "../auth/decorators/current-user.decorator";
      import { Roles } from "../auth/decorators/roles.decorator";
      
      @Controller("users")
      export class UsersController {
        constructor(private readonly usersService: UsersService) {}
      
        // Public endpoint — no auth required
        @Public()
        @Get("count")
        getUserCount() {
          return this.usersService.count();
        }
      
        // Protected — requires valid JWT (global guard)
        @Get("me")
        getProfile(@CurrentUser() user: AuthUser) {
          return this.usersService.findOne(user.id);
        }
      
        // Protected — requires valid JWT + specific role
        @Get(":id")
        @Roles("admin")
        findOne(@Param("id", ParseIntPipe) id: number) {
          return this.usersService.findOne(id);
        }
      }
      ```
      
      **Why good:** Global guard protects all routes by default, `@Public()` opts out specific endpoints, `@CurrentUser()` extracts typed user from request, `@Roles()` adds role-based access control
      
      ---
      
      ## Pattern 3: Role-Based Access Control (RBAC)
      
      ### Roles Guard
      
      ```typescript
      // auth/guards/roles.guard.ts
      import {
        Injectable,
        CanActivate,
        ExecutionContext,
        ForbiddenException,
      } from "@nestjs/common";
      import { Reflector } from "@nestjs/core";
      
      const ROLES_KEY = "roles";
      
      @Injectable()
      export class RolesGuard implements CanActivate {
        constructor(private readonly reflector: Reflector) {}
      
        canActivate(context: ExecutionContext): boolean {
          const requiredRoles = this.reflector.getAllAndOverride<string[]>(
            ROLES_KEY,
            [context.getHandler(), context.getClass()],
          );
      
          // No roles required — allow access
          if (!requiredRoles || requiredRoles.length === 0) {
            return true;
          }
      
          const { user } = context.switchToHttp().getRequest();
      
          if (!user) {
            throw new ForbiddenException("No user found on request");
          }
      
          const hasRole = requiredRoles.includes(user.role);
          if (!hasRole) {
            throw new ForbiddenException(
              `Required roles: ${requiredRoles.join(", ")}`,
            );
          }
      
          return true;
        }
      }
      ```
      
      ### Roles Decorator
      
      ```typescript
      // auth/decorators/roles.decorator.ts
      import { SetMetadata } from "@nestjs/common";
      
      const ROLES_KEY = "roles";
      
      export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
      ```
      
      ### Register as Global Guard
      
      ```typescript
      // app.module.ts
      import { APP_GUARD } from "@nestjs/core";
      
      @Module({
        providers: [
          { provide: APP_GUARD, useClass: JwtAuthGuard },
          { provide: APP_GUARD, useClass: RolesGuard },
        ],
      })
      export class AppModule {}
      ```
      
      **Why good:** Global guards apply to all routes automatically, `Reflector` reads metadata from decorators, guards compose in order (JWT first, then roles), explicit error messages for debugging
      
      ---
      
      _For database patterns, see [database.md](database.md). For testing, see [testing.md](testing.md). For advanced patterns, see [advanced.md](advanced.md)._
      
    • core.md 14.7 KB
      # NestJS Core Examples
      
      > Complete code examples for NestJS modules, controllers, services, DTOs, and exception handling. See [SKILL.md](../SKILL.md) for core concepts.
      
      **More examples:** See [database.md](database.md), [auth.md](auth.md), [testing.md](testing.md), and [advanced.md](advanced.md).
      
      ---
      
      ## Pattern 1: Feature Module — Complete CRUD
      
      A complete feature module with controller, service, DTOs, and proper error handling.
      
      ### Module
      
      ```typescript
      // products/products.module.ts
      import { Module } from "@nestjs/common";
      import { ProductsController } from "./products.controller";
      import { ProductsService } from "./products.service";
      
      @Module({
        controllers: [ProductsController],
        providers: [ProductsService],
        exports: [ProductsService],
      })
      export class ProductsModule {}
      ```
      
      ### DTOs
      
      ```typescript
      // products/dto/create-product.dto.ts
      import {
        IsString,
        IsNumber,
        IsPositive,
        IsOptional,
        IsArray,
        MaxLength,
        Min,
      } from "class-validator";
      import { Type } from "class-transformer";
      
      const MAX_NAME_LENGTH = 200;
      const MAX_DESCRIPTION_LENGTH = 2000;
      const MIN_PRICE = 0.01;
      
      export class CreateProductDto {
        @IsString()
        @MaxLength(MAX_NAME_LENGTH)
        name: string;
      
        @IsOptional()
        @IsString()
        @MaxLength(MAX_DESCRIPTION_LENGTH)
        description?: string;
      
        @IsNumber({ maxDecimalPlaces: 2 })
        @IsPositive()
        @Min(MIN_PRICE)
        price: number;
      
        @IsOptional()
        @IsArray()
        @IsString({ each: true })
        tags?: string[];
      
        @IsOptional()
        @IsNumber()
        @Min(0)
        @Type(() => Number)
        stock?: number;
      }
      ```
      
      ```typescript
      // products/dto/update-product.dto.ts
      import { PartialType } from "@nestjs/mapped-types";
      import { CreateProductDto } from "./create-product.dto";
      
      export class UpdateProductDto extends PartialType(CreateProductDto) {}
      ```
      
      ```typescript
      // products/dto/query-products.dto.ts
      import { IsOptional, IsString, IsNumber, Min, IsEnum } from "class-validator";
      import { Type } from "class-transformer";
      
      const DEFAULT_PAGE = 1;
      const DEFAULT_LIMIT = 20;
      const MAX_LIMIT = 100;
      
      enum SortOrder {
        Asc = "asc",
        Desc = "desc",
      }
      
      export class QueryProductsDto {
        @IsOptional()
        @IsString()
        search?: string;
      
        @IsOptional()
        @Type(() => Number)
        @IsNumber()
        @Min(1)
        page?: number = DEFAULT_PAGE;
      
        @IsOptional()
        @Type(() => Number)
        @IsNumber()
        @Min(1)
        limit?: number = DEFAULT_LIMIT;
      
        @IsOptional()
        @IsEnum(SortOrder)
        sort?: SortOrder = SortOrder.Desc;
      }
      ```
      
      **Why good:** Named constants for limits, `@Type(() => Number)` converts query string params, enum for sort order, defaults in class properties, `PartialType` reuses create DTO
      
      ### Service
      
      ```typescript
      // products/products.service.ts
      import {
        Injectable,
        NotFoundException,
        ConflictException,
        Logger,
      } from "@nestjs/common";
      import type { CreateProductDto } from "./dto/create-product.dto";
      import type { UpdateProductDto } from "./dto/update-product.dto";
      import type { QueryProductsDto } from "./dto/query-products.dto";
      
      interface Product {
        id: number;
        name: string;
        description?: string;
        price: number;
        tags: string[];
        stock: number;
        createdAt: Date;
        updatedAt: Date;
      }
      
      const DEFAULT_STOCK = 0;
      
      @Injectable()
      export class ProductsService {
        private readonly logger = new Logger(ProductsService.name);
        private readonly products: Product[] = [];
        private nextId = 1;
      
        findAll(query: QueryProductsDto) {
          let results = [...this.products];
      
          // Filter by search
          if (query.search) {
            const searchLower = query.search.toLowerCase();
            results = results.filter(
              (p) =>
                p.name.toLowerCase().includes(searchLower) ||
                p.description?.toLowerCase().includes(searchLower),
            );
          }
      
          // Sort
          results.sort((a, b) => {
            const modifier = query.sort === "asc" ? 1 : -1;
            return (a.createdAt.getTime() - b.createdAt.getTime()) * modifier;
          });
      
          // Paginate
          const page = query.page ?? 1;
          const limit = query.limit ?? 20;
          const start = (page - 1) * limit;
          const items = results.slice(start, start + limit);
      
          return {
            items,
            total: results.length,
            page,
            limit,
            totalPages: Math.ceil(results.length / limit),
          };
        }
      
        findOne(id: number): Product {
          const product = this.products.find((p) => p.id === id);
          if (!product) {
            throw new NotFoundException(`Product with id ${id} not found`);
          }
          return product;
        }
      
        create(dto: CreateProductDto): Product {
          const existing = this.products.find((p) => p.name === dto.name);
          if (existing) {
            throw new ConflictException(`Product "${dto.name}" already exists`);
          }
      
          const now = new Date();
          const product: Product = {
            id: this.nextId++,
            name: dto.name,
            description: dto.description,
            price: dto.price,
            tags: dto.tags ?? [],
            stock: dto.stock ?? DEFAULT_STOCK,
            createdAt: now,
            updatedAt: now,
          };
      
          this.products.push(product);
          this.logger.log(`Created product: ${product.name} (id: ${product.id})`);
          return product;
        }
      
        update(id: number, dto: UpdateProductDto): Product {
          const product = this.findOne(id);
          Object.assign(product, dto, { updatedAt: new Date() });
          this.logger.log(`Updated product: ${product.name} (id: ${product.id})`);
          return product;
        }
      
        remove(id: number): void {
          const index = this.products.findIndex((p) => p.id === id);
          if (index === -1) {
            throw new NotFoundException(`Product with id ${id} not found`);
          }
          const [removed] = this.products.splice(index, 1);
          this.logger.log(`Removed product: ${removed.name} (id: ${removed.id})`);
        }
      }
      ```
      
      **Why good:** Logger scoped to class name, paginated results with metadata, NestJS exceptions for error cases, typed DTOs via `import type`, named constant for default stock
      
      ### Controller
      
      ```typescript
      // products/products.controller.ts
      import {
        Controller,
        Get,
        Post,
        Put,
        Delete,
        Body,
        Param,
        Query,
        ParseIntPipe,
        HttpCode,
        HttpStatus,
      } from "@nestjs/common";
      import { ProductsService } from "./products.service";
      import { CreateProductDto } from "./dto/create-product.dto";
      import { UpdateProductDto } from "./dto/update-product.dto";
      import { QueryProductsDto } from "./dto/query-products.dto";
      
      @Controller("products")
      export class ProductsController {
        constructor(private readonly productsService: ProductsService) {}
      
        @Get()
        findAll(@Query() query: QueryProductsDto) {
          return this.productsService.findAll(query);
        }
      
        @Get(":id")
        findOne(@Param("id", ParseIntPipe) id: number) {
          return this.productsService.findOne(id);
        }
      
        @Post()
        @HttpCode(HttpStatus.CREATED)
        create(@Body() createProductDto: CreateProductDto) {
          return this.productsService.create(createProductDto);
        }
      
        @Put(":id")
        update(
          @Param("id", ParseIntPipe) id: number,
          @Body() updateProductDto: UpdateProductDto,
        ) {
          return this.productsService.update(id, updateProductDto);
        }
      
        @Delete(":id")
        @HttpCode(HttpStatus.NO_CONTENT)
        remove(@Param("id", ParseIntPipe) id: number) {
          return this.productsService.remove(id);
        }
      }
      ```
      
      **Why good:** Controller is thin (delegates all logic to service), query DTO handles pagination/search/sort, `ParseIntPipe` validates IDs, explicit HTTP status codes
      
      ---
      
      ## Pattern 2: Dynamic Modules
      
      Dynamic modules accept configuration at import time.
      
      ### Good Example — Configurable Module
      
      ```typescript
      // mailer/mailer.module.ts
      import { Module, DynamicModule } from "@nestjs/common";
      import { MailerService } from "./mailer.service";
      
      const MAILER_OPTIONS = "MAILER_OPTIONS";
      
      interface MailerOptions {
        host: string;
        port: number;
        from: string;
      }
      
      @Module({})
      export class MailerModule {
        static forRoot(options: MailerOptions): DynamicModule {
          return {
            module: MailerModule,
            global: true,
            providers: [
              {
                provide: MAILER_OPTIONS,
                useValue: options,
              },
              MailerService,
            ],
            exports: [MailerService],
          };
        }
      }
      ```
      
      ```typescript
      // mailer/mailer.service.ts
      import { Injectable, Inject } from "@nestjs/common";
      
      const MAILER_OPTIONS = "MAILER_OPTIONS";
      
      interface MailerOptions {
        host: string;
        port: number;
        from: string;
      }
      
      @Injectable()
      export class MailerService {
        constructor(
          @Inject(MAILER_OPTIONS) private readonly options: MailerOptions,
        ) {}
      
        async sendEmail(to: string, subject: string, body: string) {
          // Use this.options to send email via your mail transport
        }
      }
      ```
      
      ```typescript
      // app.module.ts — Usage
      @Module({
        imports: [
          MailerModule.forRoot({
            host: "smtp.example.com",
            port: 587,
            from: "noreply@example.com",
          }),
        ],
      })
      export class AppModule {}
      ```
      
      **Why good:** `forRoot()` pattern for configurable modules, `global: true` makes it available everywhere, token-based injection for options, clear options interface
      
      ---
      
      ## Pattern 3: Global Exception Filter
      
      ### Good Example — Structured Error Responses
      
      ```typescript
      // filters/all-exceptions.filter.ts
      import {
        ExceptionFilter,
        Catch,
        ArgumentsHost,
        HttpException,
        HttpStatus,
        Logger,
      } from "@nestjs/common";
      import type { Request, Response } from "express";
      
      interface ErrorResponse {
        statusCode: number;
        message: string | string[];
        error: string;
        timestamp: string;
        path: string;
      }
      
      @Catch()
      export class AllExceptionsFilter implements ExceptionFilter {
        private readonly logger = new Logger(AllExceptionsFilter.name);
      
        catch(exception: unknown, host: ArgumentsHost) {
          const ctx = host.switchToHttp();
          const response = ctx.getResponse<Response>();
          const request = ctx.getRequest<Request>();
      
          let status: number;
          let message: string | string[];
          let error: string;
      
          if (exception instanceof HttpException) {
            status = exception.getStatus();
            const exceptionResponse = exception.getResponse();
      
            if (typeof exceptionResponse === "object" && exceptionResponse !== null) {
              const responseObj = exceptionResponse as Record<string, unknown>;
              message =
                (responseObj.message as string | string[]) ?? exception.message;
              error = (responseObj.error as string) ?? "Error";
            } else {
              message = exception.message;
              error = "Error";
            }
          } else {
            status = HttpStatus.INTERNAL_SERVER_ERROR;
            message = "Internal server error";
            error = "Internal Server Error";
      
            // Log unexpected errors with stack trace
            this.logger.error(
              `Unexpected error: ${exception instanceof Error ? exception.message : "Unknown"}`,
              exception instanceof Error ? exception.stack : undefined,
            );
          }
      
          const errorResponse: ErrorResponse = {
            statusCode: status,
            message,
            error,
            timestamp: new Date().toISOString(),
            path: request.url,
          };
      
          response.status(status).json(errorResponse);
        }
      }
      ```
      
      ```typescript
      // main.ts — Register globally
      import { NestFactory } from "@nestjs/core";
      import { ValidationPipe } from "@nestjs/common";
      import { AppModule } from "./app.module";
      import { AllExceptionsFilter } from "./filters/all-exceptions.filter";
      
      const PORT = 3000;
      
      async function bootstrap() {
        const app = await NestFactory.create(AppModule);
      
        app.useGlobalPipes(
          new ValidationPipe({
            whitelist: true,
            forbidNonWhitelisted: true,
            transform: true,
          }),
        );
      
        app.useGlobalFilters(new AllExceptionsFilter());
      
        await app.listen(PORT);
      }
      bootstrap();
      ```
      
      **Why good:** Catches all exceptions (not just HttpException), preserves validation error arrays from ValidationPipe, logs unexpected errors with stack trace, consistent response shape
      
      ---
      
      ## Pattern 4: Nested DTOs and Validation Groups
      
      ### Good Example — Complex Validation
      
      ```typescript
      // orders/dto/create-order.dto.ts
      import {
        IsString,
        IsNumber,
        IsArray,
        ValidateNested,
        ArrayMinSize,
        IsPositive,
        IsOptional,
      } from "class-validator";
      import { Type } from "class-transformer";
      
      const MIN_ORDER_ITEMS = 1;
      
      class OrderItemDto {
        @IsString()
        productId: string;
      
        @IsNumber()
        @IsPositive()
        quantity: number;
      }
      
      class ShippingAddressDto {
        @IsString()
        street: string;
      
        @IsString()
        city: string;
      
        @IsString()
        state: string;
      
        @IsString()
        zipCode: string;
      
        @IsOptional()
        @IsString()
        country?: string;
      }
      
      export class CreateOrderDto {
        @IsArray()
        @ArrayMinSize(MIN_ORDER_ITEMS)
        @ValidateNested({ each: true })
        @Type(() => OrderItemDto)
        items: OrderItemDto[];
      
        @ValidateNested()
        @Type(() => ShippingAddressDto)
        shippingAddress: ShippingAddressDto;
      
        @IsOptional()
        @IsString()
        notes?: string;
      }
      ```
      
      **Why good:** `@ValidateNested` with `@Type()` validates nested objects, `{ each: true }` validates each array element, named constant for minimum items, complex real-world shape
      
      ### Bad Example — Flat DTO for Nested Data
      
      ```typescript
      // BAD: Flattening nested data into one level
      export class CreateOrderDto {
        @IsString()
        productId: string;
      
        @IsNumber()
        quantity: number;
      
        @IsString()
        shippingStreet: string;
      
        @IsString()
        shippingCity: string;
        // ... becomes unwieldy for complex data
      }
      ```
      
      **Why bad:** Flat DTOs don't represent the actual data shape, can't validate nested arrays, becomes unmanageable with complex payloads
      
      ---
      
      ## Pattern 5: Custom Providers
      
      ### Good Example — Factory, Value, and Class Providers
      
      ```typescript
      // config/providers.ts
      import { ConfigService } from "@nestjs/config";
      
      // Value provider — static configuration
      export const APP_NAME_PROVIDER = {
        provide: "APP_NAME",
        useValue: "My NestJS App",
      };
      
      // Class provider — swap implementations
      export const LOGGER_PROVIDER = {
        provide: "LOGGER",
        useClass: process.env.NODE_ENV === "production" ? JsonLogger : ConsoleLogger,
      };
      
      // Factory provider — async initialization with dependencies
      export const DATABASE_PROVIDER = {
        provide: "DATABASE_CONNECTION",
        useFactory: async (configService: ConfigService) => {
          const host = configService.get<string>("DB_HOST");
          const port = configService.get<number>("DB_PORT");
          return createDatabaseConnection({ host, port });
        },
        inject: [ConfigService],
      };
      ```
      
      ```typescript
      // Usage — Inject with @Inject token
      import { Injectable, Inject } from "@nestjs/common";
      
      @Injectable()
      export class AppService {
        constructor(
          @Inject("APP_NAME") private readonly appName: string,
          @Inject("DATABASE_CONNECTION") private readonly db: DatabaseConnection,
        ) {}
      
        getAppInfo() {
          return { name: this.appName, dbConnected: this.db.isConnected() };
        }
      }
      ```
      
      **Why good:** Value providers for constants, class providers for environment-specific implementations, factory providers for async initialization with injected dependencies, string tokens for non-class providers
      
      ---
      
      _For database patterns, see [database.md](database.md). For auth patterns, see [auth.md](auth.md). For testing, see [testing.md](testing.md). For advanced patterns, see [advanced.md](advanced.md)._
      
    • database.md 10.6 KB
      # NestJS Database Integration Examples
      
      > NestJS DI integration patterns for database access. These examples show how to wrap your ORM in NestJS modules and services — not the ORM API itself. See [SKILL.md](../SKILL.md) for core concepts.
      
      ---
      
      ## Pattern 1: Prisma Integration
      
      NestJS integrates Prisma via a custom service that wraps `PrismaClient` with lifecycle hooks.
      
      ### PrismaService
      
      ```typescript
      // prisma/prisma.service.ts
      import {
        Injectable,
        OnModuleInit,
        OnModuleDestroy,
        Logger,
      } from "@nestjs/common";
      import { PrismaClient } from "@prisma/client";
      
      @Injectable()
      export class PrismaService
        extends PrismaClient
        implements OnModuleInit, OnModuleDestroy
      {
        private readonly logger = new Logger(PrismaService.name);
      
        async onModuleInit() {
          await this.$connect();
          this.logger.log("Prisma connected to database");
        }
      
        async onModuleDestroy() {
          await this.$disconnect();
          this.logger.log("Prisma disconnected from database");
        }
      }
      ```
      
      ### PrismaModule
      
      ```typescript
      // prisma/prisma.module.ts
      import { Module, Global } from "@nestjs/common";
      import { PrismaService } from "./prisma.service";
      
      @Global()
      @Module({
        providers: [PrismaService],
        exports: [PrismaService],
      })
      export class PrismaModule {}
      ```
      
      **Why good:** `@Global()` makes PrismaService available everywhere without importing PrismaModule, lifecycle hooks handle connect/disconnect, extends PrismaClient for full API access
      
      ### Service Using Prisma
      
      ```typescript
      // users/users.service.ts
      import {
        Injectable,
        NotFoundException,
        ConflictException,
      } from "@nestjs/common";
      import { PrismaService } from "../prisma/prisma.service";
      import { Prisma } from "@prisma/client";
      import type { CreateUserDto } from "./dto/create-user.dto";
      import type { UpdateUserDto } from "./dto/update-user.dto";
      
      const DEFAULT_PAGE = 1;
      const DEFAULT_LIMIT = 20;
      
      @Injectable()
      export class UsersService {
        constructor(private readonly prisma: PrismaService) {}
      
        async findAll(page = DEFAULT_PAGE, limit = DEFAULT_LIMIT) {
          const skip = (page - 1) * limit;
      
          const [items, total] = await Promise.all([
            this.prisma.user.findMany({
              skip,
              take: limit,
              orderBy: { createdAt: "desc" },
              select: {
                id: true,
                email: true,
                name: true,
                role: true,
                createdAt: true,
              },
            }),
            this.prisma.user.count(),
          ]);
      
          return {
            items,
            total,
            page,
            limit,
            totalPages: Math.ceil(total / limit),
          };
        }
      
        async findOne(id: number) {
          const user = await this.prisma.user.findUnique({
            where: { id },
            include: { posts: true },
          });
      
          if (!user) {
            throw new NotFoundException(`User with id ${id} not found`);
          }
      
          return user;
        }
      
        async create(dto: CreateUserDto) {
          try {
            return await this.prisma.user.create({
              data: dto,
            });
          } catch (error) {
            if (
              error instanceof Prisma.PrismaClientKnownRequestError &&
              error.code === "P2002"
            ) {
              throw new ConflictException(
                `User with email ${dto.email} already exists`,
              );
            }
            throw error;
          }
        }
      
        async update(id: number, dto: UpdateUserDto) {
          try {
            return await this.prisma.user.update({
              where: { id },
              data: dto,
            });
          } catch (error) {
            if (
              error instanceof Prisma.PrismaClientKnownRequestError &&
              error.code === "P2025"
            ) {
              throw new NotFoundException(`User with id ${id} not found`);
            }
            throw error;
          }
        }
      
        async remove(id: number) {
          try {
            await this.prisma.user.delete({ where: { id } });
          } catch (error) {
            if (
              error instanceof Prisma.PrismaClientKnownRequestError &&
              error.code === "P2025"
            ) {
              throw new NotFoundException(`User with id ${id} not found`);
            }
            throw error;
          }
        }
      }
      ```
      
      **Why good:** Handles Prisma-specific error codes (P2002 for unique constraint, P2025 for not found), `select` for query optimization, `Promise.all` for parallel count + data queries, paginated response
      
      ---
      
      ## Pattern 2: TypeORM Integration
      
      TypeORM provides decorator-based entity definitions and a repository pattern.
      
      ### Entity
      
      ```typescript
      // users/entities/user.entity.ts
      import {
        Entity,
        PrimaryGeneratedColumn,
        Column,
        CreateDateColumn,
        UpdateDateColumn,
        OneToMany,
      } from "typeorm";
      import { Post } from "../../posts/entities/post.entity";
      
      @Entity("users")
      export class User {
        @PrimaryGeneratedColumn()
        id: number;
      
        @Column({ unique: true })
        email: string;
      
        @Column()
        name: string;
      
        @Column({ select: false })
        password: string;
      
        @Column({ default: "user" })
        role: string;
      
        @OneToMany(() => Post, (post) => post.author)
        posts: Post[];
      
        @CreateDateColumn()
        createdAt: Date;
      
        @UpdateDateColumn()
        updatedAt: Date;
      }
      ```
      
      ### Module with TypeORM
      
      ```typescript
      // users/users.module.ts
      import { Module } from "@nestjs/common";
      import { TypeOrmModule } from "@nestjs/typeorm";
      import { User } from "./entities/user.entity";
      import { UsersController } from "./users.controller";
      import { UsersService } from "./users.service";
      
      @Module({
        imports: [TypeOrmModule.forFeature([User])],
        controllers: [UsersController],
        providers: [UsersService],
        exports: [UsersService],
      })
      export class UsersModule {}
      ```
      
      ### Service with Repository
      
      ```typescript
      // users/users.service.ts
      import { Injectable, NotFoundException } from "@nestjs/common";
      import { InjectRepository } from "@nestjs/typeorm";
      import { Repository } from "typeorm";
      import { User } from "./entities/user.entity";
      import type { CreateUserDto } from "./dto/create-user.dto";
      
      @Injectable()
      export class UsersService {
        constructor(
          @InjectRepository(User)
          private readonly usersRepository: Repository<User>,
        ) {}
      
        async findAll(): Promise<User[]> {
          return this.usersRepository.find({
            order: { createdAt: "DESC" },
          });
        }
      
        async findOne(id: number): Promise<User> {
          const user = await this.usersRepository.findOne({
            where: { id },
            relations: ["posts"],
          });
      
          if (!user) {
            throw new NotFoundException(`User with id ${id} not found`);
          }
      
          return user;
        }
      
        async findByEmail(email: string): Promise<User | null> {
          return this.usersRepository.findOne({
            where: { email },
            select: ["id", "email", "name", "password", "role"],
          });
        }
      
        async create(dto: CreateUserDto): Promise<User> {
          const user = this.usersRepository.create(dto);
          return this.usersRepository.save(user);
        }
      
        async update(id: number, dto: Partial<CreateUserDto>): Promise<User> {
          const user = await this.findOne(id);
          Object.assign(user, dto);
          return this.usersRepository.save(user);
        }
      
        async remove(id: number): Promise<void> {
          const result = await this.usersRepository.delete(id);
          if (result.affected === 0) {
            throw new NotFoundException(`User with id ${id} not found`);
          }
        }
      }
      ```
      
      **Why good:** `@InjectRepository` for DI-friendly repository access, `select` for sensitive fields (password), relation loading, `create()` + `save()` pattern for proper entity lifecycle
      
      ### Root Module with TypeORM Config
      
      ```typescript
      // app.module.ts
      import { Module } from "@nestjs/common";
      import { TypeOrmModule } from "@nestjs/typeorm";
      import { ConfigModule, ConfigService } from "@nestjs/config";
      
      @Module({
        imports: [
          ConfigModule.forRoot({ isGlobal: true }),
          TypeOrmModule.forRootAsync({
            inject: [ConfigService],
            useFactory: (configService: ConfigService) => ({
              type: "postgres",
              host: configService.get("DB_HOST"),
              port: configService.get<number>("DB_PORT"),
              username: configService.get("DB_USER"),
              password: configService.get("DB_PASSWORD"),
              database: configService.get("DB_NAME"),
              autoLoadEntities: true,
              synchronize: configService.get("NODE_ENV") !== "production",
            }),
          }),
        ],
      })
      export class AppModule {}
      ```
      
      **Why good:** Async config from environment variables, `autoLoadEntities` avoids manual entity registration, `synchronize` disabled in production (use migrations)
      
      ---
      
      ## Pattern 3: Transactions
      
      ### Prisma Transaction
      
      ```typescript
      // orders/orders.service.ts
      @Injectable()
      export class OrdersService {
        constructor(private readonly prisma: PrismaService) {}
      
        async createOrder(dto: CreateOrderDto) {
          return this.prisma.$transaction(async (tx) => {
            // Create order
            const order = await tx.order.create({
              data: {
                userId: dto.userId,
                status: "pending",
              },
            });
      
            // Create order items and update stock
            for (const item of dto.items) {
              await tx.orderItem.create({
                data: {
                  orderId: order.id,
                  productId: item.productId,
                  quantity: item.quantity,
                },
              });
      
              // Decrement stock
              const product = await tx.product.update({
                where: { id: item.productId },
                data: { stock: { decrement: item.quantity } },
              });
      
              if (product.stock < 0) {
                throw new BadRequestException(
                  `Insufficient stock for product ${item.productId}`,
                );
              }
            }
      
            return order;
          });
        }
      }
      ```
      
      **Why good:** Interactive transaction ensures atomicity, stock check inside transaction prevents race conditions, error rolls back all changes
      
      ### TypeORM Transaction
      
      ```typescript
      @Injectable()
      export class OrdersService {
        constructor(
          @InjectRepository(Order)
          private readonly ordersRepository: Repository<Order>,
          private readonly dataSource: DataSource,
        ) {}
      
        async createOrder(dto: CreateOrderDto) {
          const queryRunner = this.dataSource.createQueryRunner();
          await queryRunner.connect();
          await queryRunner.startTransaction();
      
          try {
            const order = queryRunner.manager.create(Order, {
              userId: dto.userId,
              status: "pending",
            });
            await queryRunner.manager.save(order);
      
            for (const item of dto.items) {
              const orderItem = queryRunner.manager.create(OrderItem, {
                orderId: order.id,
                ...item,
              });
              await queryRunner.manager.save(orderItem);
            }
      
            await queryRunner.commitTransaction();
            return order;
          } catch (error) {
            await queryRunner.rollbackTransaction();
            throw error;
          } finally {
            await queryRunner.release();
          }
        }
      }
      ```
      
      **Why good:** Explicit transaction control with commit/rollback, `finally` ensures queryRunner release, manager operations within transaction scope
      
      ---
      
      _For auth patterns, see [auth.md](auth.md). For testing, see [testing.md](testing.md). For advanced patterns, see [advanced.md](advanced.md)._
      
    • testing.md 16.6 KB
      # NestJS Testing Examples
      
      > Unit testing services with mocks, e2e testing with supertest. See [SKILL.md](../SKILL.md) for core concepts.
      
      ---
      
      ## Pattern 1: Unit Testing Services
      
      Use NestJS `Test.createTestingModule()` to create an isolated testing module with mocked dependencies.
      
      ### Good Example — Testing a Service
      
      ```typescript
      // users/users.service.spec.ts
      import { Test, TestingModule } from "@nestjs/testing";
      import { NotFoundException, ConflictException } from "@nestjs/common";
      import { UsersService } from "./users.service";
      import { PrismaService } from "../prisma/prisma.service";
      
      const MOCK_USER = {
        id: 1,
        email: "alice@example.com",
        name: "Alice",
        role: "user",
        createdAt: new Date(),
        updatedAt: new Date(),
      };
      
      const MOCK_CREATE_DTO = {
        email: "bob@example.com",
        name: "Bob",
        password: "hashed_password",
      };
      
      describe("UsersService", () => {
        let service: UsersService;
        let prisma: PrismaService;
      
        const mockPrismaService = {
          user: {
            findUnique: jest.fn(),
            findMany: jest.fn(),
            create: jest.fn(),
            update: jest.fn(),
            delete: jest.fn(),
            count: jest.fn(),
          },
        };
      
        beforeEach(async () => {
          const module: TestingModule = await Test.createTestingModule({
            providers: [
              UsersService,
              {
                provide: PrismaService,
                useValue: mockPrismaService,
              },
            ],
          }).compile();
      
          service = module.get<UsersService>(UsersService);
          prisma = module.get<PrismaService>(PrismaService);
      
          // Reset mocks between tests
          jest.clearAllMocks();
        });
      
        describe("findOne", () => {
          it("should return a user when found", async () => {
            mockPrismaService.user.findUnique.mockResolvedValue(MOCK_USER);
      
            const result = await service.findOne(1);
      
            expect(result).toEqual(MOCK_USER);
            expect(mockPrismaService.user.findUnique).toHaveBeenCalledWith({
              where: { id: 1 },
              include: { posts: true },
            });
          });
      
          it("should throw NotFoundException when user not found", async () => {
            mockPrismaService.user.findUnique.mockResolvedValue(null);
      
            await expect(service.findOne(999)).rejects.toThrow(NotFoundException);
          });
        });
      
        describe("create", () => {
          it("should create and return a new user", async () => {
            const newUser = {
              id: 2,
              ...MOCK_CREATE_DTO,
              createdAt: new Date(),
              updatedAt: new Date(),
            };
            mockPrismaService.user.create.mockResolvedValue(newUser);
      
            const result = await service.create(MOCK_CREATE_DTO);
      
            expect(result).toEqual(newUser);
            expect(mockPrismaService.user.create).toHaveBeenCalledWith({
              data: MOCK_CREATE_DTO,
            });
          });
      
          it("should throw ConflictException on duplicate email", async () => {
            const prismaError = new Error("Unique constraint failed");
            Object.assign(prismaError, { code: "P2002" });
            mockPrismaService.user.create.mockRejectedValue(prismaError);
      
            await expect(service.create(MOCK_CREATE_DTO)).rejects.toThrow(
              ConflictException,
            );
          });
        });
      
        describe("remove", () => {
          it("should delete a user", async () => {
            mockPrismaService.user.delete.mockResolvedValue(MOCK_USER);
      
            await service.remove(1);
      
            expect(mockPrismaService.user.delete).toHaveBeenCalledWith({
              where: { id: 1 },
            });
          });
      
          it("should throw NotFoundException when deleting non-existent user", async () => {
            const prismaError = new Error("Record not found");
            Object.assign(prismaError, { code: "P2025" });
            mockPrismaService.user.delete.mockRejectedValue(prismaError);
      
            await expect(service.remove(999)).rejects.toThrow(NotFoundException);
          });
        });
      });
      ```
      
      **Why good:** `Test.createTestingModule` mirrors the real module, mock Prisma methods with `jest.fn()`, test both happy path and error cases, `jest.clearAllMocks()` prevents test pollution, named constants for test data
      
      ### Bad Example — Testing with Real Database
      
      ```typescript
      // BAD: Unit test hitting real database
      describe("UsersService", () => {
        let service: UsersService;
      
        beforeAll(async () => {
          // BAD: Using real PrismaService connects to database
          const module = await Test.createTestingModule({
            providers: [UsersService, PrismaService],
          }).compile();
      
          service = module.get(UsersService);
        });
      
        it("should find users", async () => {
          // BAD: Depends on database state
          const users = await service.findAll();
          expect(users).toBeDefined();
        });
      });
      ```
      
      **Why bad:** Unit tests should not hit real databases, tests become flaky depending on database state, slow execution, not isolated
      
      ---
      
      ## Pattern 2: Unit Testing Controllers
      
      ### Good Example — Testing Controller with Mocked Service
      
      ```typescript
      // users/users.controller.spec.ts
      import { Test, TestingModule } from "@nestjs/testing";
      import { UsersController } from "./users.controller";
      import { UsersService } from "./users.service";
      
      const MOCK_USER = {
        id: 1,
        email: "alice@example.com",
        name: "Alice",
        role: "user",
      };
      
      const MOCK_USERS_RESPONSE = {
        items: [MOCK_USER],
        total: 1,
        page: 1,
        limit: 20,
        totalPages: 1,
      };
      
      describe("UsersController", () => {
        let controller: UsersController;
      
        const mockUsersService = {
          findAll: jest.fn().mockResolvedValue(MOCK_USERS_RESPONSE),
          findOne: jest.fn().mockResolvedValue(MOCK_USER),
          create: jest.fn().mockResolvedValue(MOCK_USER),
          update: jest.fn().mockResolvedValue(MOCK_USER),
          remove: jest.fn().mockResolvedValue(undefined),
        };
      
        beforeEach(async () => {
          const module: TestingModule = await Test.createTestingModule({
            controllers: [UsersController],
            providers: [
              {
                provide: UsersService,
                useValue: mockUsersService,
              },
            ],
          }).compile();
      
          controller = module.get<UsersController>(UsersController);
          jest.clearAllMocks();
        });
      
        describe("findAll", () => {
          it("should return paginated users", async () => {
            const result = await controller.findAll({ page: 1, limit: 20 });
      
            expect(result).toEqual(MOCK_USERS_RESPONSE);
            expect(mockUsersService.findAll).toHaveBeenCalledWith(1, 20);
          });
        });
      
        describe("findOne", () => {
          it("should return a single user", async () => {
            const result = await controller.findOne(1);
      
            expect(result).toEqual(MOCK_USER);
            expect(mockUsersService.findOne).toHaveBeenCalledWith(1);
          });
        });
      
        describe("create", () => {
          it("should create a user", async () => {
            const dto = {
              email: "alice@example.com",
              name: "Alice",
              password: "password123",
            };
            const result = await controller.create(dto);
      
            expect(result).toEqual(MOCK_USER);
            expect(mockUsersService.create).toHaveBeenCalledWith(dto);
          });
        });
      });
      ```
      
      **Why good:** Controllers are thin, so tests verify delegation to service, mock service prevents testing business logic twice, tests verify correct arguments passed to service
      
      ---
      
      ## Pattern 3: Unit Testing Guards
      
      ### Good Example — Testing a Custom Guard
      
      ```typescript
      // auth/guards/roles.guard.spec.ts
      import { ExecutionContext, ForbiddenException } from "@nestjs/common";
      import { Reflector } from "@nestjs/core";
      import { RolesGuard } from "./roles.guard";
      
      function createMockExecutionContext(
        user: { role: string } | undefined,
        requiredRoles: string[] | undefined,
      ): ExecutionContext {
        const mockReflector = {
          getAllAndOverride: jest.fn().mockReturnValue(requiredRoles),
        };
      
        const context = {
          switchToHttp: () => ({
            getRequest: () => ({ user }),
          }),
          getHandler: () => jest.fn(),
          getClass: () => jest.fn(),
        } as unknown as ExecutionContext;
      
        return context;
      }
      
      describe("RolesGuard", () => {
        let guard: RolesGuard;
        let reflector: Reflector;
      
        beforeEach(() => {
          reflector = new Reflector();
          guard = new RolesGuard(reflector);
        });
      
        it("should allow access when no roles are required", () => {
          jest.spyOn(reflector, "getAllAndOverride").mockReturnValue(undefined);
          const context = createMockExecutionContext({ role: "user" }, undefined);
      
          expect(guard.canActivate(context)).toBe(true);
        });
      
        it("should allow access when user has required role", () => {
          jest.spyOn(reflector, "getAllAndOverride").mockReturnValue(["admin"]);
          const context = createMockExecutionContext({ role: "admin" }, ["admin"]);
      
          expect(guard.canActivate(context)).toBe(true);
        });
      
        it("should deny access when user lacks required role", () => {
          jest.spyOn(reflector, "getAllAndOverride").mockReturnValue(["admin"]);
          const context = createMockExecutionContext({ role: "user" }, ["admin"]);
      
          expect(() => guard.canActivate(context)).toThrow(ForbiddenException);
        });
      });
      ```
      
      **Why good:** Tests all three paths (no roles required, role match, role mismatch), mock ExecutionContext matches NestJS interface, verifies guard throws correct exception type
      
      ---
      
      ## Pattern 4: E2E Testing with Supertest
      
      ### Good Example — Full API Integration Test
      
      ```typescript
      // test/users.e2e-spec.ts
      import { Test, TestingModule } from "@nestjs/testing";
      import { INestApplication, ValidationPipe, HttpStatus } from "@nestjs/common";
      import * as request from "supertest";
      import { AppModule } from "../src/app.module";
      import { PrismaService } from "../src/prisma/prisma.service";
      
      describe("UsersController (e2e)", () => {
        let app: INestApplication;
        let prisma: PrismaService;
      
        beforeAll(async () => {
          const moduleFixture: TestingModule = await Test.createTestingModule({
            imports: [AppModule],
          }).compile();
      
          app = moduleFixture.createNestApplication();
      
          // Mirror production configuration
          app.useGlobalPipes(
            new ValidationPipe({
              whitelist: true,
              forbidNonWhitelisted: true,
              transform: true,
            }),
          );
      
          await app.init();
          prisma = app.get(PrismaService);
        });
      
        afterAll(async () => {
          await app.close();
        });
      
        beforeEach(async () => {
          // Clean database between tests
          await prisma.user.deleteMany();
        });
      
        describe("POST /users", () => {
          it("should create a user with valid data", () => {
            return request(app.getHttpServer())
              .post("/users")
              .send({
                email: "alice@example.com",
                name: "Alice",
                password: "securepassword",
              })
              .expect(HttpStatus.CREATED)
              .expect((res) => {
                expect(res.body).toHaveProperty("id");
                expect(res.body.email).toBe("alice@example.com");
                expect(res.body.name).toBe("Alice");
                // Password should NOT be in response
                expect(res.body).not.toHaveProperty("password");
              });
          });
      
          it("should reject invalid email", () => {
            return request(app.getHttpServer())
              .post("/users")
              .send({
                email: "not-an-email",
                name: "Alice",
                password: "securepassword",
              })
              .expect(HttpStatus.BAD_REQUEST)
              .expect((res) => {
                expect(res.body.message).toContain("email must be an email");
              });
          });
      
          it("should reject unknown properties", () => {
            return request(app.getHttpServer())
              .post("/users")
              .send({
                email: "alice@example.com",
                name: "Alice",
                password: "securepassword",
                isAdmin: true, // Unknown property
              })
              .expect(HttpStatus.BAD_REQUEST);
          });
      
          it("should reject duplicate email", async () => {
            // Create first user
            await request(app.getHttpServer())
              .post("/users")
              .send({
                email: "alice@example.com",
                name: "Alice",
                password: "securepassword",
              })
              .expect(HttpStatus.CREATED);
      
            // Try duplicate
            return request(app.getHttpServer())
              .post("/users")
              .send({
                email: "alice@example.com",
                name: "Alice 2",
                password: "securepassword",
              })
              .expect(HttpStatus.CONFLICT);
          });
        });
      
        describe("GET /users/:id", () => {
          it("should return a user", async () => {
            const createRes = await request(app.getHttpServer()).post("/users").send({
              email: "alice@example.com",
              name: "Alice",
              password: "securepassword",
            });
      
            return request(app.getHttpServer())
              .get(`/users/${createRes.body.id}`)
              .expect(HttpStatus.OK)
              .expect((res) => {
                expect(res.body.email).toBe("alice@example.com");
              });
          });
      
          it("should return 404 for non-existent user", () => {
            return request(app.getHttpServer())
              .get("/users/99999")
              .expect(HttpStatus.NOT_FOUND);
          });
        });
      
        describe("DELETE /users/:id", () => {
          it("should delete a user", async () => {
            const createRes = await request(app.getHttpServer()).post("/users").send({
              email: "alice@example.com",
              name: "Alice",
              password: "securepassword",
            });
      
            await request(app.getHttpServer())
              .delete(`/users/${createRes.body.id}`)
              .expect(HttpStatus.NO_CONTENT);
      
            // Verify deleted
            return request(app.getHttpServer())
              .get(`/users/${createRes.body.id}`)
              .expect(HttpStatus.NOT_FOUND);
          });
        });
      });
      ```
      
      **Why good:** Mirrors production config (ValidationPipe), tests HTTP status codes and response shapes, cleans database between tests, verifies validation rejects invalid data, tests the full request-response cycle
      
      ### Bad Example — E2E Test Without Validation Setup
      
      ```typescript
      // BAD: Missing ValidationPipe — validation not tested
      describe("UsersController (e2e)", () => {
        let app: INestApplication;
      
        beforeAll(async () => {
          const moduleFixture = await Test.createTestingModule({
            imports: [AppModule],
          }).compile();
      
          app = moduleFixture.createNestApplication();
          // BAD: No ValidationPipe — invalid data will pass through
          await app.init();
        });
      
        it("should validate input", () => {
          // This test PASSES even though email is invalid
          // because ValidationPipe is not configured
          return request(app.getHttpServer())
            .post("/users")
            .send({ email: "not-an-email" })
            .expect(201); // BAD: Should be 400
        });
      });
      ```
      
      **Why bad:** E2E tests without ValidationPipe don't test validation, invalid data passes through, tests give false confidence
      
      ---
      
      ## Pattern 5: Testing Auth-Protected Routes
      
      ### Good Example — E2E with Authentication
      
      ```typescript
      // test/auth.e2e-spec.ts
      import { Test, TestingModule } from "@nestjs/testing";
      import { INestApplication, ValidationPipe, HttpStatus } from "@nestjs/common";
      import * as request from "supertest";
      import { AppModule } from "../src/app.module";
      
      describe("Auth (e2e)", () => {
        let app: INestApplication;
        let accessToken: string;
      
        beforeAll(async () => {
          const moduleFixture: TestingModule = await Test.createTestingModule({
            imports: [AppModule],
          }).compile();
      
          app = moduleFixture.createNestApplication();
          app.useGlobalPipes(
            new ValidationPipe({ whitelist: true, transform: true }),
          );
          await app.init();
        });
      
        afterAll(async () => {
          await app.close();
        });
      
        it("should register a new user", () => {
          return request(app.getHttpServer())
            .post("/auth/register")
            .send({
              email: "test@example.com",
              name: "Test User",
              password: "securepassword",
            })
            .expect(HttpStatus.CREATED);
        });
      
        it("should login and receive JWT", async () => {
          const res = await request(app.getHttpServer())
            .post("/auth/login")
            .send({
              email: "test@example.com",
              password: "securepassword",
            })
            .expect(HttpStatus.OK);
      
          expect(res.body).toHaveProperty("accessToken");
          accessToken = res.body.accessToken;
        });
      
        it("should access protected route with token", () => {
          return request(app.getHttpServer())
            .get("/users/me")
            .set("Authorization", `Bearer ${accessToken}`)
            .expect(HttpStatus.OK)
            .expect((res) => {
              expect(res.body.email).toBe("test@example.com");
            });
        });
      
        it("should reject protected route without token", () => {
          return request(app.getHttpServer())
            .get("/users/me")
            .expect(HttpStatus.UNAUTHORIZED);
        });
      
        it("should reject invalid token", () => {
          return request(app.getHttpServer())
            .get("/users/me")
            .set("Authorization", "Bearer invalid-token")
            .expect(HttpStatus.UNAUTHORIZED);
        });
      });
      ```
      
      **Why good:** Tests full auth flow (register, login, access protected route), verifies both valid and invalid tokens, uses `.set('Authorization', ...)` for bearer token, tests guard behavior end-to-end
      
      ---
      
      _For core patterns, see [core.md](core.md). For database patterns, see [database.md](database.md). For advanced patterns, see [advanced.md](advanced.md)._
      
  • reference.md 9.8 KB
    # NestJS Reference
    
    > CLI commands, project structure, decorator tables, pipes, exceptions, and provider scopes. See [SKILL.md](SKILL.md) for core concepts, decision frameworks, and red flags.
    
    ---
    
    ## CLI Commands
    
    ### Project Scaffolding
    
    ```bash
    # Create new project
    nest new my-project
    
    # Create with specific package manager
    nest new my-project --package-manager pnpm
    ```
    
    ### Code Generation
    
    ```bash
    # Generate a complete CRUD resource (module + controller + service + DTOs)
    nest generate resource users
    nest g res users
    
    # Generate individual components
    nest generate module users          # nest g mo users
    nest generate controller users      # nest g co users
    nest generate service users         # nest g s users
    nest generate guard auth            # nest g gu auth
    nest generate interceptor logging   # nest g itc logging
    nest generate pipe validation       # nest g pi validation
    nest generate filter http-exception # nest g f http-exception
    nest generate middleware logger     # nest g mi logger
    nest generate decorator roles       # nest g d roles
    nest generate class dto/create-user # nest g cl dto/create-user
    ```
    
    ### Build and Run
    
    ```bash
    # Development
    nest start --watch
    
    # Debug mode
    nest start --debug --watch
    
    # Production build
    nest build
    
    # Run production
    node dist/main.js
    ```
    
    ---
    
    ## Standard Project Structure
    
    ```
    src/
    ├── main.ts                          # App entry point, bootstrap
    ├── app.module.ts                    # Root module
    ├── app.controller.ts                # Root controller (health check)
    ├── app.service.ts                   # Root service
    │
    ├── config/                          # Configuration
    │   ├── app.config.ts                # App config (registerAs)
    │   └── database.config.ts           # Database config
    │
    ├── common/                          # Shared utilities
    │   ├── decorators/                  # Custom decorators
    │   ├── filters/                     # Exception filters
    │   ├── guards/                      # Auth/role guards
    │   ├── interceptors/                # Response/logging interceptors
    │   ├── middleware/                   # HTTP middleware
    │   └── pipes/                       # Custom pipes
    │
    ├── prisma/                          # Database (Prisma)
    │   ├── prisma.module.ts
    │   └── prisma.service.ts
    │
    ├── auth/                            # Auth feature module
    │   ├── auth.module.ts
    │   ├── auth.controller.ts
    │   ├── auth.service.ts
    │   ├── dto/
    │   │   └── login.dto.ts
    │   ├── guards/
    │   │   ├── jwt-auth.guard.ts
    │   │   └── roles.guard.ts
    │   ├── strategies/
    │   │   ├── jwt.strategy.ts
    │   │   └── local.strategy.ts
    │   └── decorators/
    │       ├── current-user.decorator.ts
    │       ├── public.decorator.ts
    │       └── roles.decorator.ts
    │
    ├── users/                           # Users feature module
    │   ├── users.module.ts
    │   ├── users.controller.ts
    │   ├── users.service.ts
    │   ├── dto/
    │   │   ├── create-user.dto.ts
    │   │   ├── update-user.dto.ts
    │   │   └── query-users.dto.ts
    │   └── entities/
    │       └── user.entity.ts           # TypeORM entity (if using TypeORM)
    │
    └── orders/                          # Orders feature module
        ├── orders.module.ts
        ├── orders.controller.ts
        ├── orders.service.ts
        └── dto/
            └── create-order.dto.ts
    
    test/
    ├── app.e2e-spec.ts                  # E2E tests
    └── jest-e2e.json                    # E2E test config
    ```
    
    ---
    
    ## Decorator Quick Reference
    
    ### Class Decorators
    
    | Decorator               | Purpose                        | Example                                             |
    | ----------------------- | ------------------------------ | --------------------------------------------------- |
    | `@Module({...})`        | Define a module                | `@Module({ controllers: [...], providers: [...] })` |
    | `@Controller(path?)`    | Define a controller            | `@Controller('users')`                              |
    | `@Injectable()`         | Mark class for DI              | `@Injectable() export class UsersService {}`        |
    | `@Global()`             | Make module globally available | `@Global() @Module({...})`                          |
    | `@Catch(...exceptions)` | Exception filter               | `@Catch(HttpException)`                             |
    
    ### Route Decorators
    
    | Decorator        | HTTP Method | Example          |
    | ---------------- | ----------- | ---------------- |
    | `@Get(path?)`    | GET         | `@Get(':id')`    |
    | `@Post(path?)`   | POST        | `@Post()`        |
    | `@Put(path?)`    | PUT         | `@Put(':id')`    |
    | `@Patch(path?)`  | PATCH       | `@Patch(':id')`  |
    | `@Delete(path?)` | DELETE      | `@Delete(':id')` |
    | `@All(path?)`    | All methods | `@All('*')`      |
    
    ### Parameter Decorators
    
    | Decorator        | Extracts         | Example                                           |
    | ---------------- | ---------------- | ------------------------------------------------- |
    | `@Body(key?)`    | Request body     | `@Body() dto: CreateUserDto`                      |
    | `@Param(key?)`   | Route params     | `@Param('id', ParseIntPipe) id: number`           |
    | `@Query(key?)`   | Query string     | `@Query('page') page: string`                     |
    | `@Headers(key?)` | Request headers  | `@Headers('authorization') auth: string`          |
    | `@Req()`         | Express Request  | `@Req() req: Request`                             |
    | `@Res()`         | Express Response | `@Res() res: Response` (disables NestJS response) |
    | `@Ip()`          | Client IP        | `@Ip() ip: string`                                |
    | `@Session()`     | Session object   | `@Session() session: Record<string, any>`         |
    
    ### Handler Decorators
    
    | Decorator                 | Purpose                 | Example                                 |
    | ------------------------- | ----------------------- | --------------------------------------- |
    | `@HttpCode(status)`       | Set response status     | `@HttpCode(HttpStatus.NO_CONTENT)`      |
    | `@Header(name, value)`    | Set response header     | `@Header('Cache-Control', 'none')`      |
    | `@Redirect(url, code?)`   | Redirect response       | `@Redirect('https://example.com', 301)` |
    | `@UseGuards(...guards)`   | Apply guards            | `@UseGuards(AuthGuard)`                 |
    | `@UseInterceptors(...i)`  | Apply interceptors      | `@UseInterceptors(LoggingInterceptor)`  |
    | `@UsePipes(...pipes)`     | Apply pipes             | `@UsePipes(new ValidationPipe())`       |
    | `@UseFilters(...filters)` | Apply exception filters | `@UseFilters(HttpExceptionFilter)`      |
    
    ### Metadata Decorators
    
    | Decorator                | Purpose            | Example                            |
    | ------------------------ | ------------------ | ---------------------------------- |
    | `@SetMetadata(key, val)` | Set route metadata | `@SetMetadata('roles', ['admin'])` |
    
    ---
    
    ## Built-in Pipes
    
    | Pipe               | Purpose                             |
    | ------------------ | ----------------------------------- |
    | `ValidationPipe`   | Validates DTO with class-validator  |
    | `ParseIntPipe`     | Converts string to integer          |
    | `ParseFloatPipe`   | Converts string to float            |
    | `ParseBoolPipe`    | Converts string to boolean          |
    | `ParseUUIDPipe`    | Validates UUID format               |
    | `ParseDatePipe`    | Converts string to Date (NestJS 11) |
    | `ParseEnumPipe`    | Validates enum membership           |
    | `ParseArrayPipe`   | Parses and validates arrays         |
    | `DefaultValuePipe` | Sets default for undefined params   |
    
    ---
    
    ## Built-in HTTP Exceptions
    
    | Exception                       | Status Code |
    | ------------------------------- | ----------- |
    | `BadRequestException`           | 400         |
    | `UnauthorizedException`         | 401         |
    | `ForbiddenException`            | 403         |
    | `NotFoundException`             | 404         |
    | `MethodNotAllowedException`     | 405         |
    | `NotAcceptableException`        | 406         |
    | `RequestTimeoutException`       | 408         |
    | `ConflictException`             | 409         |
    | `GoneException`                 | 410         |
    | `PayloadTooLargeException`      | 413         |
    | `UnsupportedMediaTypeException` | 415         |
    | `UnprocessableEntityException`  | 422         |
    | `InternalServerErrorException`  | 500         |
    | `NotImplementedException`       | 501         |
    | `BadGatewayException`           | 502         |
    | `ServiceUnavailableException`   | 503         |
    | `GatewayTimeoutException`       | 504         |
    
    ---
    
    ## Provider Scopes
    
    | Scope                 | Lifetime      | Use Case                                    |
    | --------------------- | ------------- | ------------------------------------------- |
    | `DEFAULT` (Singleton) | App lifetime  | Most services, shared state                 |
    | `REQUEST`             | Per request   | Request-specific data (user context)        |
    | `TRANSIENT`           | Per injection | Stateful providers that shouldn't be shared |
    
    ```typescript
    @Injectable({ scope: Scope.REQUEST })
    export class RequestScopedService {
      // New instance per HTTP request
    }
    ```
    
    ---
    
    ## Module Metadata
    
    | Property      | Type                     | Purpose                          |
    | ------------- | ------------------------ | -------------------------------- |
    | `imports`     | `Module[]`               | Other modules to import          |
    | `controllers` | `Controller[]`           | Controllers in this module       |
    | `providers`   | `Provider[]`             | Services/providers for DI        |
    | `exports`     | `(Provider \| string)[]` | Providers available to importers |
    
    ---
    
    > For decision frameworks and red flags, see [SKILL.md](SKILL.md).
    
  • SKILL.md 14.4 KB
    ---
    name: api-framework-nestjs
    description: NestJS backend framework - modules, controllers, services, DI, guards, pipes, interceptors, exception filters, middleware, DTOs with class-validator
    ---
    
    # NestJS Patterns
    
    > **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 enable `ValidationPipe` globally with `whitelist: true`, never put business logic in controllers, never instantiate services with `new`. 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](examples/core.md) — Feature modules, CRUD, DTOs, dynamic modules, exception filters, custom providers
    - [examples/database.md](examples/database.md) — NestJS DI patterns for database integration, transactions
    - [examples/auth.md](examples/auth.md) — Passport.js integration, JWT strategy, auth guards, RBAC
    - [examples/testing.md](examples/testing.md) — Unit testing with `Test.createTestingModule`, e2e with supertest
    - [examples/advanced.md](examples/advanced.md) — Interceptors, custom pipes, custom decorators, config, CQRS, Swagger
    - [reference.md](reference.md) — CLI commands, project structure, decorator tables, decision frameworks
    
    ---
    
    <philosophy>
    
    ## 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:**
    
    1. **Modularity** — Group related controllers, services, and providers into feature modules. Modules are the primary organizational unit.
    2. **Dependency injection** — Never instantiate services manually. Declare them as `@Injectable()` and let NestJS resolve the dependency graph via constructor injection.
    3. **Decorator-driven** — Decorators (`@Controller`, `@Get`, `@Body`, `@UseGuards`) attach metadata that NestJS uses to build routing, validation, and middleware pipelines.
    4. **Separation of concerns** — Controllers handle HTTP request/response. Services handle business logic. Guards handle authorization. Pipes handle validation/transformation. Filters handle exceptions.
    5. **Convention over configuration** — Follow NestJS conventions (one module per feature, one controller per resource, DTOs for validation) to get batteries-included functionality.
    
    </philosophy>
    
    ---
    
    <patterns>
    
    ## 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.
    
    ```typescript
    // 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](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.
    
    ```typescript
    @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.
    
    ```typescript
    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) {}
    ```
    
    ```typescript
    // 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](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.
    
    ```typescript
    @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):
    
    ```typescript
    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](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.
    
    ```typescript
    // 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](examples/core.md).
    
    ---
    
    ### Guards and Middleware
    
    Guards decide whether a request proceeds (authorization). Middleware runs before routing (logging, CORS).
    
    ```typescript
    // 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](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).
    
    ```typescript
    @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](examples/advanced.md) for logging, caching, and custom pipe patterns.
    
    </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 `ValidationPipe` globally (DTOs are not validated)
    - Using `any` for request body instead of typed DTOs
    - Instantiating services with `new` instead of constructor injection
    - Throwing raw `Error` instead 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: true` on ValidationPipe (mass-assignment vulnerability)
    - Using `@Res()` decorator outside streaming scenarios (opts out of NestJS response handling)
    - Not using `PartialType` / `PickType` / `OmitType` for 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>` or `Observable<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 DI
    - `ValidationPipe` with `transform: true` converts 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 HttpException
    - `IntrinsicException` (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 (`/*splat` instead of `/*`)
    - NestJS 11: SWC is a supported opt-in compiler via `nest-cli.json` (`"builder": "swc"`) — 20x faster builds than tsc
    - NestJS 11: `ParseDatePipe` is 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>
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related