NestJS Development Guide

Dev Tools

NestJS Node.js server-side framework development guide.

Install

openclaw skills install nestjs-dev-guide

NestJS Development Guide

NestJS is a progressive framework for building efficient, scalable Node.js server-side applications. Built with TypeScript, it integrates OOP/FP/FRP, uses Express (default) or Fastify under the hood, and its architecture is deeply inspired by Angular.

Version: v11+ | Requires Node.js ≥20

Trigger Scenarios

Use when the user needs to build a backend application with NestJS, create REST APIs, use decorators and dependency injection, build microservices, configure Swagger documentation, or write NestJS Controllers/Providers/Modules/Guards/Pipes/Interceptors. Trigger keywords include "nest", "nestjs", "create API", "backend service", "decorator injection", "guard", "pipe", "interceptor", "swagger", etc.

Quick Start

npm i -g @nestjs/cli
nest new my-project
cd my-project && npm run start:dev

Or use npx @nestjs/cli@latest new my-project to avoid global installation.

Project File Structure

src/
├── main.ts              # Application entry, NestFactory.create()
├── app.module.ts        # Root module
├── app.controller.ts    # Base controller
├── app.controller.spec.ts
└── app.service.ts       # Base service

Core Concepts

1. Module

The @Module() decorator defines modules — the logical boundaries and DI container scopes of the application.

@Module({
  imports: [OtherModule],        // Import other modules
  controllers: [CatsController], // Register controllers
  providers: [CatsService],      // Register providers
  exports: [CatsService],        // Export providers for use by other modules
})
export class CatsModule {}
  • Feature modules: Organized by domain, e.g., CatsModule, UsersModule
  • Shared modules: Expose providers via the exports array
  • Global modules: @Global() decorator, but not recommended to overuse
  • Dynamic modules: static forRoot() returns a runtime-configured module
  • Module re-exporting: Put imported modules in exports

2. Controller

@Controller('prefix') + HTTP method decorators define routes:

@Controller('cats')
export class CatsController {
  @Get()        findAll() {}
  @Get(':id')   findOne(@Param('id') id: string) {}
  @Post()       create(@Body() dto: CreateCatDto) {}
  @Put(':id')   update(@Param('id') id: string, @Body() dto: UpdateCatDto) {}
  @Delete(':id') remove(@Param('id') id: string) {}
}

Parameter Decorators Cheat Sheet:

DecoratorExtracts
@Param(key?)Route parameters
@Body(key?)Request body
@Query(key?)Query parameters
@Headers(name?)Request headers
@Req()Native Request object
@Res()Native Response (manual management required)
@Ip()Client IP
@HostParam()Subdomain parameters

Route wildcards: @Get('ab{*splat}cd'), @Get('prefix/*')

Status codes and headers: @HttpCode(204), @Header('Cache-Control', 'none')

Redirect: @Redirect('url', 301) or return { url, statusCode }

DTO Definition: Use class (not interface), because interfaces disappear after compilation and cannot provide runtime type metadata:

export class CreateCatDto {
  name: string;
  age: number;
  breed: string;
}

3. Provider

Classes marked with @Injectable(), injected via constructor:

@Injectable()
export class CatsService {
  private cats: Cat[] = [];
  create(cat: Cat) { this.cats.push(cat); }
  findAll(): Cat[] { return this.cats; }
}

@Controller('cats')
export class CatsController {
  constructor(private catsService: CatsService) {}  // Auto-injected
}

Custom Providers (using full syntax in @Module.providers):

SyntaxPurpose
{ provide: Token, useClass: Class }Standard class provider
{ provide: Token, useValue: value }Constant value / mock object
{ provide: Token, useFactory: fn, inject: [...] }Factory function
{ provide: Token, useExisting: OtherToken }Alias

Scope: Default SINGLETON (application-level singleton), can be set to REQUEST (request-level).

Optional Providers: @Optional() decorator, no error if dependency is missing.

4. Middleware

Plain Express middleware class implementing NestMiddleware, bound in consumer:

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) { /* ... */ next(); }
}

// Apply in module
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(LoggerMiddleware).forRoutes('cats');
  }
}

5. Pipe — Validation & Transformation

Implements PipeTransform, performs validation and/or data transformation. Execution order: Middleware → Guards → Interceptor(pre) → Pipe → Handler → Interceptor(post) → Exception Filter.

Built-in pipes: ValidationPipe, ParseIntPipe, ParseBoolPipe, ParseArrayPipe, ParseUUIDPipe

ValidationPipe (Recommended)

npm i class-validator class-transformer
// main.ts — global enable
app.useGlobalPipes(new ValidationPipe({
  whitelist: true,            // Automatically strip undeclared properties
  forbidNonWhitelisted: true, // Error if non-whitelisted properties exist
  transform: true,            // Auto type conversion (string→number etc.)
  disableErrorMessages: false,
}));
// DTO using validation decorators
import { IsString, IsInt, IsEmail, Min, Max } from 'class-validator';

export class CreateUserDto {
  @IsString() @IsNotEmpty()
  name: string;

  @IsInt() @Min(0) @Max(150)
  age: number;

  @IsEmail()
  email: string;
}

Zod validation alternative:

const schema = z.object({ name: z.string(), age: z.number() });
@UsePipes(new ZodValidationPipe(schema))

6. Guard — Authorization Control

Implements CanActivate, returns boolean determining whether to proceed. Executes after middleware and before interceptors/pipes.

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();
    return validateRequest(request);
  }
}

Role guard + custom metadata:

// Create decorator
import { Reflector } from '@nestjs/core';
export const Roles = Reflector.createDecorator<string[]>();

// Usage
@Post()
@Roles(['admin'])
async create() {}

// Read in guard
@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}
  canActivate(context: ExecutionContext): boolean {
    const roles = this.reflector.get(Roles, context.getHandler());
    if (!roles) return true;
    const { user } = context.switchToHttp().getRequest();
    return roles.some(r => user.roles?.includes(r));
  }
}

Binding scope: @UseGuards(RolesGuard) on class/method; app.useGlobalGuards() global; via APP_GUARD token for DI.

7. Interceptor — AOP Aspect

Implements NestInterceptor. Inserts logic before/after methods, can transform results/exceptions.

// Logging interceptor
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(ctx: ExecutionContext, next: CallHandler): Observable<any> {
    const now = Date.now();
    return next.handle().pipe(
      tap(() => console.log(`After... ${Date.now() - now}ms`))
    );
  }
}

// Uniform response wrapper
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, {data: T}> {
  intercept(ctx: ExecutionContext, next: CallHandler) {
    return next.handle().pipe(map(data => ({ data })));
  }
}

8. Exception Filter

// Built-in exceptions
throw new BadRequestException('message');
throw new NotFoundException();
throw new UnauthorizedException();
// 20+ types: Forbidden, Conflict, InternalServerError, BadGateway, ...

// Custom
throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
throw new HttpException({ status: 403, error: 'custom' }, 403);

Custom filter:

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const res = ctx.getResponse<Response>();
    const status = exception.getStatus();
    res.status(status).json({ statusCode: status, timestamp: new Date().toISOString() });
  }
}

Nest CLI

nest new <name>          # Create new project
nest g resource <name>   # Generate complete CRUD resource (module/controller/service/dto)
nest g module <name>     # Generate module
nest g controller <name> # Generate controller
nest g service <name>    # Generate service
nest g guard <name>      # Generate guard
nest g pipe <name>       # Generate pipe
nest g interceptor <name># Generate interceptor
nest g filter <name>     # Generate filter
nest build               # Compile
nest start               # Start
nest info                # System information

OpenAPI (Swagger)

npm i @nestjs/swagger
// main.ts
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';

const config = new DocumentBuilder()
  .setTitle('API Docs').setVersion('1.0').addBearerAuth().build();

const documentFactory = () => SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, documentFactory);
// Access http://localhost:3000/api

Decorators: @ApiTags(), @ApiOperation(), @ApiProperty(), @ApiResponse(), @ApiBearerAuth()

Common Technology Modules

Database

Nest recommends using TypeORM (@nestjs/typeorm) or Prisma:

npm i @nestjs/typeorm typeorm pg
// app.module.ts
TypeOrmModule.forRoot({ type: 'postgres', host: 'localhost', database: 'mydb', autoLoadEntities: true, synchronize: true })

// feature module
TypeOrmModule.forFeature([User])

Configuration Management

npm i @nestjs/config
// app.module.ts
ConfigModule.forRoot({ isGlobal: true, envFilePath: '.env' })

// Usage
constructor(private configService: ConfigService) {}
this.configService.get<string>('DATABASE_URL')

Task Scheduling

npm i @nestjs/schedule
@Cron('45 * * * * *')  handleCron() {}
@Interval(10000)       handleInterval() {}
@Timeout(5000)         handleTimeout() {}

Caching

npm i @nestjs/cache-manager cache-manager

CacheModule.register() + @UseInterceptors(CacheInterceptor)

Global Registration vs DI Injection

Most Nest components support two global registration approaches:

// Method 1: Register directly in main.ts (cannot inject dependencies)
app.useGlobalPipes(new ValidationPipe());
app.useGlobalGuards(new RolesGuard());
app.useGlobalInterceptors(new LoggingInterceptor());
app.useGlobalFilters(new HttpExceptionFilter());

// Method 2: Register via Token in AppModule (supports DI)
providers: [
  { provide: APP_PIPE, useClass: ValidationPipe },
  { provide: APP_GUARD, useClass: RolesGuard },
  { provide: APP_INTERCEPTOR, useClass: LoggingInterceptor },
  { provide: APP_FILTER, useClass: HttpExceptionFilter },
]

Request Lifecycle Order

1. Middleware
2. Guard
3. Interceptor (pre)
4. Pipe
5. Route Handler
6. Interceptor (post)
7. Exception Filter (if exception occurs)

Recommended Directory Structure

src/
├── common/            # Shared: decorators, filters, guards, interceptors, pipes
├── config/            # Configuration
├── modules/
│   ├── users/
│   │   ├── dto/
│   │   ├── entities/
│   │   ├── users.controller.ts
│   │   ├── users.service.ts
│   │   └── users.module.ts
│   └── auth/
│       ├── dto/
│       ├── strategies/
│       ├── auth.controller.ts
│       ├── auth.service.ts
│       └── auth.module.ts
├── app.module.ts
└── main.ts

Detailed API reference see references/api-reference.md.