Skip to main content
Technology & EngineeringApi Frameworks285 lines

NestJS

NestJS: progressive Node.js framework with decorators, dependency injection, modules, guards, pipes, and interceptors for scalable APIs

Quick Summary24 lines
You are an expert in building APIs with NestJS.

## Key Points

- Organize code by feature modules, not by technical layer — keep controllers, services, DTOs, and entities for a domain together in one module.
- Always use DTOs with `class-validator` and the global `ValidationPipe` for input validation — never trust raw request bodies.
- Use custom providers and injection tokens for external dependencies (database clients, HTTP clients) to keep services testable and swappable.
- Circular dependencies between modules — resolve with `forwardRef(() => OtherModule)` or restructure to extract shared logic into a common module.
- Forgetting to add services to the `providers` array in the module decorator, leading to "Nest can't resolve dependencies" errors at startup.

## Quick Example

```bash
npm install -g @nestjs/cli
nest new my-api
cd my-api
npm run start:dev
```

```bash
npm install class-validator class-transformer
```
skilldb get api-frameworks-skills/NestJSFull skill: 285 lines
Paste into your CLAUDE.md or agent config

NestJS — API Framework

You are an expert in building APIs with NestJS.

Core Philosophy

Overview

NestJS is a progressive Node.js framework for building scalable server-side applications. It uses TypeScript by default and combines elements of OOP, FP, and FRP. Built on top of Express (or optionally Fastify), NestJS provides an opinionated architecture inspired by Angular — using decorators, dependency injection, modules, and a layered structure that enforces separation of concerns. It is widely adopted for enterprise-grade APIs.

Setup & Configuration

npm install -g @nestjs/cli
nest new my-api
cd my-api
npm run start:dev

Project structure:

src/
├── app.module.ts        # Root module
├── app.controller.ts    # Root controller
├── app.service.ts       # Root service
├── main.ts              # Entry point
└── users/
    ├── users.module.ts
    ├── users.controller.ts
    ├── users.service.ts
    ├── dto/
    │   └── create-user.dto.ts
    └── entities/
        └── user.entity.ts

Entry point (src/main.ts):

import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.useGlobalPipes(new ValidationPipe({
    whitelist: true,            // Strip unknown properties
    forbidNonWhitelisted: true, // Throw on unknown properties
    transform: true,            // Auto-transform payloads to DTO instances
  }));

  app.enableCors();
  app.setGlobalPrefix('api/v1');

  await app.listen(3000);
}
bootstrap();

Core Patterns

Modules

Modules organize the application into cohesive blocks. Every NestJS app has at least one root module.

import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';

@Module({
  controllers: [UsersController],
  providers: [UsersService],
  exports: [UsersService], // Make available to other modules
})
export class UsersModule {}

Controllers and Routing

import {
  Controller, Get, Post, Put, Delete,
  Param, Body, Query, HttpCode, HttpStatus,
  ParseIntPipe,
} from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Post()
  @HttpCode(HttpStatus.CREATED)
  create(@Body() createUserDto: CreateUserDto) {
    return this.usersService.create(createUserDto);
  }

  @Get()
  findAll(@Query('page', new ParseIntPipe({ optional: true })) page?: number) {
    return this.usersService.findAll(page);
  }

  @Get(':id')
  findOne(@Param('id', ParseIntPipe) id: number) {
    return this.usersService.findOne(id);
  }

  @Put(':id')
  update(@Param('id', ParseIntPipe) id: number, @Body() updateUserDto: UpdateUserDto) {
    return this.usersService.update(id, updateUserDto);
  }

  @Delete(':id')
  @HttpCode(HttpStatus.NO_CONTENT)
  remove(@Param('id', ParseIntPipe) id: number) {
    return this.usersService.remove(id);
  }
}

Validation with DTOs and class-validator

npm install class-validator class-transformer
import { IsString, IsEmail, IsOptional, IsInt, Min, MinLength } from 'class-validator';

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

  @IsEmail()
  email: string;

  @IsOptional()
  @IsInt()
  @Min(0)
  age?: number;
}

UpdateUserDto using PartialType to make all fields optional:

import { PartialType } from '@nestjs/mapped-types';
import { CreateUserDto } from './create-user.dto';

export class UpdateUserDto extends PartialType(CreateUserDto) {}

Services and Dependency Injection

import { Injectable, NotFoundException } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';

@Injectable()
export class UsersService {
  private users = new Map<number, any>();
  private nextId = 1;

  create(dto: CreateUserDto) {
    const user = { id: this.nextId++, ...dto };
    this.users.set(user.id, user);
    return user;
  }

  findOne(id: number) {
    const user = this.users.get(id);
    if (!user) throw new NotFoundException(`User #${id} not found`);
    return user;
  }

  findAll(page = 1) {
    return [...this.users.values()];
  }

  update(id: number, dto: Partial<CreateUserDto>) {
    const user = this.findOne(id);
    Object.assign(user, dto);
    return user;
  }

  remove(id: number) {
    this.findOne(id); // throws if not found
    this.users.delete(id);
  }
}

Guards (Authentication/Authorization)

import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { SetMetadata } from '@nestjs/common';

// Custom decorator
export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);
    if (isPublic) return true;

    const request = context.switchToHttp().getRequest();
    const token = request.headers.authorization?.replace('Bearer ', '');
    if (!token) throw new UnauthorizedException();

    // Verify token and attach user to request
    request.user = this.verifyToken(token);
    return true;
  }

  private verifyToken(token: string) {
    // Token verification logic
    return { id: 1, role: 'user' };
  }
}

Interceptors

import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable, tap } from 'rxjs';

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const req = context.switchToHttp().getRequest();
    const now = Date.now();

    return next.handle().pipe(
      tap(() => {
        console.log(`${req.method} ${req.url} - ${Date.now() - now}ms`);
      }),
    );
  }
}

Best Practices

  • Organize code by feature modules, not by technical layer — keep controllers, services, DTOs, and entities for a domain together in one module.
  • Always use DTOs with class-validator and the global ValidationPipe for input validation — never trust raw request bodies.
  • Use custom providers and injection tokens for external dependencies (database clients, HTTP clients) to keep services testable and swappable.

Common Pitfalls

  • Circular dependencies between modules — resolve with forwardRef(() => OtherModule) or restructure to extract shared logic into a common module.
  • Forgetting to add services to the providers array in the module decorator, leading to "Nest can't resolve dependencies" errors at startup.

Anti-Patterns

Over-engineering for hypothetical requirements. Building for scenarios that may never materialize adds complexity without value. Solve the problem in front of you first.

Ignoring the existing ecosystem. Reinventing functionality that mature libraries already provide wastes time and introduces risk.

Premature abstraction. Creating elaborate frameworks before having enough concrete cases to know what the abstraction should look like produces the wrong abstraction.

Neglecting error handling at system boundaries. Internal code can trust its inputs, but boundaries with external systems require defensive validation.

Skipping documentation. What is obvious to you today will not be obvious to your colleague next month or to you next year.

Install this skill directly: skilldb add api-frameworks-skills

Get CLI access →