NestJS
NestJS: progressive Node.js framework with decorators, dependency injection, modules, guards, pipes, and interceptors for scalable APIs
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 linesNestJS — 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-validatorand the globalValidationPipefor 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
providersarray 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
Related Skills
Elysia
"Elysia: Bun-native web framework with type-safe routing, TypeBox validation, plugins, Eden treaty client, lifecycle hooks, and Swagger documentation"
Express.js
"Express.js with TypeScript: routing, middleware patterns, error handling, validation, authentication, static files, CORS, and production-ready configuration"
Fastify
Fastify: high-performance Node.js web framework with schema-based validation, logging, plugin architecture, and TypeScript support
Apollo GraphQL
"Apollo GraphQL: schema design, resolvers, Apollo Server, Apollo Client with React, useQuery/useMutation, caching strategies, subscriptions, and codegen"
Hono
"Hono: ultra-fast web framework for edge, serverless, and Node.js — middleware, routing, Zod validation, JWT, CORS, RPC client, and JSX support"
Koa
Koa: lightweight Node.js framework by the Express team with async/await middleware, context object, and composable architecture