Skip to content

NestJS

Progressive TypeScript-first Node.js framework for building scalable server-side applications.

CommandDescription
npm i -g @nestjs/cliInstall NestJS CLI globally
nest new project-nameCreate a new NestJS project
nest new project-name --strictCreate project with strict TypeScript
nest new project-name -p pnpmCreate project using pnpm
nest new project-name -p yarnCreate project using Yarn
CommandDescription
npm run start:devStart development server with hot reload
npm run start:debugStart with debug mode and hot reload
npm run start:prodStart production server
npm run buildBuild the project
npm run testRun unit tests
npm run test:watchRun tests in watch mode
npm run test:covRun tests with coverage report
npm run test:e2eRun end-to-end tests
npm run lintLint source code
src/
├── app.controller.ts       # Root controller
├── app.controller.spec.ts  # Controller unit test
├── app.module.ts           # Root module
├── app.service.ts          # Root service
├── main.ts                 # Application entry point
├── users/
│   ├── users.module.ts     # Feature module
│   ├── users.controller.ts # Feature controller
│   ├── users.service.ts    # Feature service
│   ├── dto/
│   │   ├── create-user.dto.ts
│   │   └── update-user.dto.ts
│   ├── entities/
│   │   └── user.entity.ts
│   └── users.controller.spec.ts
├── auth/
│   ├── auth.module.ts
│   ├── auth.guard.ts
│   ├── auth.service.ts
│   └── strategies/
│       └── jwt.strategy.ts
└── common/
    ├── filters/
    │   └── http-exception.filter.ts
    ├── interceptors/
    │   └── logging.interceptor.ts
    └── pipes/
        └── validation.pipe.ts
CommandDescription
nest generate module usersGenerate a new module
nest generate controller usersGenerate a new controller
nest generate service usersGenerate a new service
nest generate resource usersGenerate full CRUD resource (module + controller + service + DTOs)
nest generate guard authGenerate an auth guard
nest generate pipe validationGenerate a validation pipe
nest generate interceptor loggingGenerate an interceptor
nest generate middleware loggerGenerate middleware
nest generate filter http-exceptionGenerate exception filter
nest generate decorator rolesGenerate a custom decorator
nest generate class user.entityGenerate a plain class
nest generate interface userGenerate an interface
nest g res users --no-specGenerate resource without test files
nest g mo users --flatGenerate without creating subfolder
nest infoDisplay project information and dependencies
# Full CRUD resource (most common workflow)
nest g resource users

# CLI prompts:
# ? What transport layer do you use? REST API
# ? Would you like to generate CRUD entry points? Yes

# Creates:
# src/users/users.module.ts
# src/users/users.controller.ts
# src/users/users.service.ts
# src/users/dto/create-user.dto.ts
# src/users/dto/update-user.dto.ts
# src/users/entities/user.entity.ts
# src/users/users.controller.spec.ts
# Updates: src/app.module.ts (imports UsersModule)
CommandDescription
@Module({ imports: [UsersModule] })Import another module
@Module({ controllers: [UsersController] })Register controllers
@Module({ providers: [UsersService] })Register providers/services
@Module({ exports: [UsersService] })Export providers for other modules
@Global()Make module globally available (use sparingly)
import { Module, DynamicModule } from '@nestjs/common'

@Module({})
export class DatabaseModule {
  static forRoot(options: DatabaseOptions): DynamicModule {
    return {
      module: DatabaseModule,
      global: true,
      providers: [
        { provide: 'DATABASE_OPTIONS', useValue: options },
        DatabaseService,
      ],
      exports: [DatabaseService],
    }
  }

  static forRootAsync(options: DatabaseAsyncOptions): DynamicModule {
    return {
      module: DatabaseModule,
      global: true,
      imports: options.imports || [],
      providers: [
        {
          provide: 'DATABASE_OPTIONS',
          useFactory: options.useFactory,
          inject: options.inject || [],
        },
        DatabaseService,
      ],
      exports: [DatabaseService],
    }
  }
}

// Usage in AppModule
@Module({
  imports: [
    DatabaseModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: (config: ConfigService) => ({
        host: config.get('DB_HOST'),
        port: config.get('DB_PORT'),
      }),
      inject: [ConfigService],
    }),
  ],
})
export class AppModule {}
CommandDescription
@Controller('users')Define controller with route prefix
@Get()Handle GET request
@Post()Handle POST request
@Put(':id')Handle PUT request with parameter
@Patch(':id')Handle PATCH request
@Delete(':id')Handle DELETE request
@All('*')Handle all HTTP methods
CommandDescription
@Param('id') id: stringExtract route parameter
@Param('id', ParseIntPipe) id: numberExtract and parse to integer
@Query('page') page: stringExtract query parameter
@Query() query: PaginationDtoExtract all query parameters as DTO
@Body() dto: CreateUserDtoExtract and type request body
@Body('email') email: stringExtract specific body field
@Headers('authorization') auth: stringExtract request header
@Ip() ip: stringExtract client IP address
@Req() request: RequestAccess raw request object
@Res() response: ResponseAccess raw response object
@HttpCode(201)Set custom HTTP status code
@Header('Cache-Control', 'none')Set response header
@Redirect('/new-url', 301)Redirect response
import {
  Controller, Get, Post, Put, Delete, Param, Query, Body,
  HttpCode, HttpStatus, ParseIntPipe, UseGuards, UseInterceptors,
} from '@nestjs/common'
import { UsersService } from './users.service'
import { CreateUserDto } from './dto/create-user.dto'
import { UpdateUserDto } from './dto/update-user.dto'
import { JwtAuthGuard } from '../auth/jwt-auth.guard'
import { LoggingInterceptor } from '../common/interceptors/logging.interceptor'

@Controller('users')
@UseInterceptors(LoggingInterceptor)
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 = 1,
    @Query('limit', new ParseIntPipe({ optional: true })) limit = 10,
  ) {
    return this.usersService.findAll(page, limit)
  }

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

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

  @Delete(':id')
  @UseGuards(JwtAuthGuard)
  @HttpCode(HttpStatus.NO_CONTENT)
  remove(@Param('id', ParseIntPipe) id: number) {
    return this.usersService.remove(id)
  }
}
CommandDescription
@Injectable()Mark class as injectable provider
constructor(private usersService: UsersService)Inject service via constructor
@Inject('TOKEN') private configInject by custom token
{ provide: 'TOKEN', useValue: value }Register value provider
{ provide: 'TOKEN', useFactory: () => ... }Register factory provider
{ provide: 'TOKEN', useClass: MyClass }Register class provider
{ provide: 'TOKEN', useExisting: OtherService }Alias existing provider
@Optional() private service?: MyServiceOptional dependency injection
{ provide: 'TOKEN', scope: Scope.REQUEST }Request-scoped provider
import { Injectable, NotFoundException, ConflictException } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { Repository } from 'typeorm'
import { User } from './entities/user.entity'
import { CreateUserDto } from './dto/create-user.dto'
import { UpdateUserDto } from './dto/update-user.dto'

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private readonly usersRepo: Repository<User>,
  ) {}

  async create(dto: CreateUserDto): Promise<User> {
    const exists = await this.usersRepo.findOneBy({ email: dto.email })
    if (exists) {
      throw new ConflictException('Email already registered')
    }
    const user = this.usersRepo.create(dto)
    return this.usersRepo.save(user)
  }

  async findAll(page: number, limit: number) {
    const [items, total] = await this.usersRepo.findAndCount({
      skip: (page - 1) * limit,
      take: limit,
      order: { createdAt: 'DESC' },
    })
    return { items, total, page, limit }
  }

  async findOne(id: number): Promise<User> {
    const user = await this.usersRepo.findOneBy({ id })
    if (!user) throw new NotFoundException(`User #${id} not found`)
    return user
  }

  async update(id: number, dto: UpdateUserDto): Promise<User> {
    const user = await this.findOne(id)
    Object.assign(user, dto)
    return this.usersRepo.save(user)
  }

  async remove(id: number): Promise<void> {
    const result = await this.usersRepo.delete(id)
    if (result.affected === 0) {
      throw new NotFoundException(`User #${id} not found`)
    }
  }
}
npm install class-validator class-transformer
// main.ts - enable global validation
import { ValidationPipe } from '@nestjs/common'

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 types
    transformOptions: {
      enableImplicitConversion: true, // convert query strings to numbers/booleans
    },
  }))
  await app.listen(3000)
}
import {
  IsString, IsEmail, IsOptional, IsInt, Min, Max,
  MinLength, MaxLength, IsEnum, ValidateNested, IsArray,
} from 'class-validator'
import { Type } from 'class-transformer'
import { PartialType, OmitType, PickType, IntersectionType } from '@nestjs/mapped-types'

export class CreateUserDto {
  @IsString()
  @MinLength(2)
  @MaxLength(50)
  name: string

  @IsEmail()
  email: string

  @IsString()
  @MinLength(8)
  password: string

  @IsOptional()
  @IsEnum(Role)
  role?: Role

  @IsOptional()
  @ValidateNested()
  @Type(() => AddressDto)
  address?: AddressDto
}

// Auto-generate update DTO (all fields optional)
export class UpdateUserDto extends PartialType(CreateUserDto) {}

// Pick specific fields
export class LoginDto extends PickType(CreateUserDto, ['email', 'password']) {}

// Omit specific fields
export class PublicUserDto extends OmitType(CreateUserDto, ['password']) {}
CommandDescription
@UseGuards(AuthGuard)Apply guard to controller or route
@UseGuards(AuthGuard, RolesGuard)Chain multiple guards
app.useGlobalGuards(new AuthGuard())Register global guard
canActivate(context: ExecutionContext)Implement guard logic
@SetMetadata('roles', ['admin'])Set custom metadata for guards
ReflectorRead metadata in guards
npm install @nestjs/jwt @nestjs/passport passport passport-jwt
npm install -D @types/passport-jwt
// auth/auth.module.ts
@Module({
  imports: [
    UsersModule,
    JwtModule.registerAsync({
      imports: [ConfigModule],
      useFactory: (config: ConfigService) => ({
        secret: config.get('JWT_SECRET'),
        signOptions: { expiresIn: '1h' },
      }),
      inject: [ConfigService],
    }),
  ],
  providers: [AuthService, JwtStrategy],
  controllers: [AuthController],
  exports: [AuthService],
})
export class AuthModule {}

// auth/jwt.strategy.ts
import { Injectable } from '@nestjs/common'
import { PassportStrategy } from '@nestjs/passport'
import { ExtractJwt, Strategy } from 'passport-jwt'

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(config: ConfigService) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: config.get('JWT_SECRET'),
    })
  }

  validate(payload: { sub: number; email: string }) {
    return { id: payload.sub, email: payload.email }
  }
}

// auth/jwt-auth.guard.ts
import { Injectable } from '@nestjs/common'
import { AuthGuard } from '@nestjs/passport'

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}

// Usage
@Controller('profile')
@UseGuards(JwtAuthGuard)
export class ProfileController {
  @Get()
  getProfile(@Req() req) {
    return req.user // injected by JwtStrategy.validate()
  }
}
// roles.decorator.ts
import { SetMetadata } from '@nestjs/common'
export const Roles = (...roles: string[]) => SetMetadata('roles', roles)

// roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'
import { Reflector } from '@nestjs/core'

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

  canActivate(context: ExecutionContext): boolean {
    const requiredRoles = this.reflector.getAllAndOverride<string[]>('roles', [
      context.getHandler(),
      context.getClass(),
    ])
    if (!requiredRoles) return true
    const { user } = context.switchToHttp().getRequest()
    return requiredRoles.some(role => user.roles?.includes(role))
  }
}

// Usage
@Controller('admin')
@UseGuards(JwtAuthGuard, RolesGuard)
export class AdminController {
  @Get('dashboard')
  @Roles('admin')
  getDashboard() {
    return { message: 'Admin dashboard' }
  }

  @Delete('users/:id')
  @Roles('admin', 'superadmin')
  removeUser(@Param('id', ParseIntPipe) id: number) {
    return this.usersService.remove(id)
  }
}
import { Injectable, NestMiddleware } from '@nestjs/common'
import { Request, Response, NextFunction } from 'express'

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    const start = Date.now()
    res.on('finish', () => {
      const duration = Date.now() - start
      console.log(`${req.method} ${req.path} ${res.statusCode} ${duration}ms`)
    })
    next()
  }
}

// Register in module
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('*')
  }
}
import {
  Injectable, NestInterceptor, ExecutionContext, CallHandler,
} from '@nestjs/common'
import { Observable, map, tap } from 'rxjs'

// Transform response shape
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, { data: T }> {
  intercept(context: ExecutionContext, next: CallHandler): Observable<{ data: T }> {
    return next.handle().pipe(
      map(data => ({ data, timestamp: new Date().toISOString() })),
    )
  }
}

// Measure execution time
@Injectable()
export class TimingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const start = Date.now()
    return next.handle().pipe(
      tap(() => {
        const duration = Date.now() - start
        const response = context.switchToHttp().getResponse()
        response.setHeader('X-Response-Time', `${duration}ms`)
      }),
    )
  }
}
CommandDescription
npm install @nestjs/typeorm typeorm pgInstall TypeORM with PostgreSQL
TypeOrmModule.forRoot({ type: 'postgres', ... })Configure connection in AppModule
TypeOrmModule.forFeature([User])Register entity in feature module
@InjectRepository(User) private repo: Repository<User>Inject repository
npm install @prisma/client
npm install -D prisma
npx prisma init
// prisma/prisma.service.ts
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common'
import { PrismaClient } from '@prisma/client'

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
  async onModuleInit() {
    await this.$connect()
  }

  async onModuleDestroy() {
    await this.$disconnect()
  }
}

// prisma/prisma.module.ts
@Global()
@Module({
  providers: [PrismaService],
  exports: [PrismaService],
})
export class PrismaModule {}

// Usage in a service
@Injectable()
export class UsersService {
  constructor(private prisma: PrismaService) {}

  findAll() {
    return this.prisma.user.findMany()
  }

  findOne(id: number) {
    return this.prisma.user.findUniqueOrThrow({ where: { id } })
  }
}
CommandDescription
throw new BadRequestException('Invalid input')400 Bad Request
throw new UnauthorizedException('Login required')401 Unauthorized
throw new ForbiddenException('Access denied')403 Forbidden
throw new NotFoundException('User not found')404 Not Found
throw new ConflictException('Email taken')409 Conflict
throw new UnprocessableEntityException('Validation failed')422 Unprocessable
throw new InternalServerErrorException()500 Internal Server Error
import {
  ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus,
} from '@nestjs/common'
import { Request, Response } from 'express'

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp()
    const response = ctx.getResponse<Response>()
    const request = ctx.getRequest<Request>()

    const status = exception instanceof HttpException
      ? exception.getStatus()
      : HttpStatus.INTERNAL_SERVER_ERROR

    const message = exception instanceof HttpException
      ? exception.getResponse()
      : 'Internal server error'

    response.status(status).json({
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
      message: typeof message === 'string' ? message : (message as any).message,
    })
  }
}

// Register globally in main.ts
app.useGlobalFilters(new AllExceptionsFilter())
npm install @nestjs/config
// app.module.ts
import { ConfigModule, ConfigService } from '@nestjs/config'

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: ['.env.local', '.env'],
      validationSchema: Joi.object({
        NODE_ENV: Joi.string().valid('development', 'production', 'test').default('development'),
        PORT: Joi.number().default(3000),
        DATABASE_URL: Joi.string().required(),
        JWT_SECRET: Joi.string().required(),
      }),
    }),
  ],
})
export class AppModule {}

// Usage in any service
@Injectable()
export class AppService {
  constructor(private config: ConfigService) {}

  getPort(): number {
    return this.config.get<number>('PORT', 3000)
  }
}
import { Test, TestingModule } from '@nestjs/testing'
import { UsersService } from './users.service'
import { getRepositoryToken } from '@nestjs/typeorm'
import { User } from './entities/user.entity'

describe('UsersService', () => {
  let service: UsersService

  const mockRepository = {
    create: jest.fn(),
    save: jest.fn(),
    findOneBy: jest.fn(),
    findAndCount: jest.fn(),
    delete: jest.fn(),
  }

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        UsersService,
        { provide: getRepositoryToken(User), useValue: mockRepository },
      ],
    }).compile()

    service = module.get<UsersService>(UsersService)
  })

  it('should create a user', async () => {
    const dto = { name: 'Alice', email: 'alice@example.com', password: 'secret123' }
    mockRepository.findOneBy.mockResolvedValue(null)
    mockRepository.create.mockReturnValue(dto)
    mockRepository.save.mockResolvedValue({ id: 1, ...dto })

    const result = await service.create(dto)
    expect(result.id).toBe(1)
    expect(mockRepository.save).toHaveBeenCalled()
  })
})
import { Test, TestingModule } from '@nestjs/testing'
import { INestApplication, ValidationPipe } from '@nestjs/common'
import * as request from 'supertest'
import { AppModule } from '../src/app.module'

describe('UsersController (e2e)', () => {
  let app: INestApplication

  beforeAll(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule],
    }).compile()

    app = moduleFixture.createNestApplication()
    app.useGlobalPipes(new ValidationPipe({ whitelist: true }))
    await app.init()
  })

  afterAll(async () => {
    await app.close()
  })

  it('/users (POST) creates a user', () => {
    return request(app.getHttpServer())
      .post('/users')
      .send({ name: 'Alice', email: 'alice@example.com', password: 'secret123' })
      .expect(201)
      .expect((res) => {
        expect(res.body.name).toBe('Alice')
        expect(res.body.id).toBeDefined()
      })
  })

  it('/users (GET) returns paginated list', () => {
    return request(app.getHttpServer())
      .get('/users?page=1&limit=10')
      .expect(200)
      .expect((res) => {
        expect(res.body.items).toBeInstanceOf(Array)
        expect(res.body.total).toBeDefined()
      })
  })
})
  1. Use the resource generatornest g resource creates the entire CRUD scaffold (module, controller, service, DTOs, entity, test) in seconds and keeps your project consistent.

  2. Enable global ValidationPipe with whitelist — set whitelist: true and forbidNonWhitelisted: true to automatically strip or reject unexpected fields from all requests.

  3. Use mapped types for DTOsPartialType, PickType, OmitType, and IntersectionType keep your DTOs DRY by deriving update/query DTOs from the create DTO.

  4. Prefer constructor injection — let NestJS handle dependency injection through constructors rather than using manual @Inject() tokens unless you need custom providers.

  5. Keep controllers thin — controllers should only parse input and return output. Move all business logic into services for testability and reuse.

  6. Use ConfigModule for all environment access — never read process.env directly in services. Use ConfigService with validation schemas so missing variables fail fast at startup.

  7. Scope guards and interceptors appropriately — apply them at the controller or route level with decorators rather than globally, unless they truly apply everywhere.

  8. Write unit and e2e tests — unit test services with mocked dependencies using Test.createTestingModule(), and e2e test HTTP endpoints with supertest.

  9. Use Prisma or TypeORM consistently — pick one ORM and use it project-wide. Prisma offers better type safety; TypeORM offers more flexibility with decorators.

  10. Structure by feature, not by type — group files by domain (users/, auth/, orders/) rather than by type (controllers/, services/). This keeps related code together and modules self-contained.

  11. Handle errors with exceptions — throw built-in HTTP exceptions (NotFoundException, ConflictException) from services. Use exception filters for custom error formatting.

  12. Use async/await consistently — NestJS handles promises natively. Return async values from service methods and let the framework serialize the response.