TypeScript-first — full type safety out of the box.
Rich ecosystem: built-in support for TypeORM, Prisma, GraphQL, WebSockets, queues.
Excellent documentation.
Angular developers feel right at home.
Disadvantages
Steep learning curve — lots of concepts (decorators, DI, modules).
Overkill for small/simple APIs.
Heavy boilerplate compared to Express.
Decorator-heavy code can feel verbose.
Setup & CLI
Installation
npm install -g @nestjs/clinest new my-app # create new projectcd my-appnpm run start # start servernpm run start:dev # start with hot reload (nodemon)npm run start:prod # run compiled buildnpm run build # compile TypeScript → dist/npm run test # run unit testsnpm run test:e2e # run end-to-end tests
CLI Generators
nest generate module users # src/users/users.module.tsnest generate controller users # src/users/users.controller.tsnest generate service users # src/users/users.service.tsnest generate resource users # generates all 3 + DTO + CRUD boilerplatenest g mo users # shorthandnest g co usersnest g s usersnest g res usersnest g guard auth # auth.guard.tsnest g interceptor logging # logging.interceptor.tsnest g pipe validation # validation.pipe.tsnest g middleware logger # logger.middleware.ts
import { NestFactory } from '@nestjs/core';import { AppModule } from './app.module';import { ValidationPipe } from '@nestjs/common';async function bootstrap() { const app = await NestFactory.create(AppModule); // Global prefix for all routes app.setGlobalPrefix('api'); // Global validation pipe app.useGlobalPipes(new ValidationPipe({ whitelist: true, // strip unknown properties forbidNonWhitelisted: true, transform: true, // auto-transform types })); // Enable CORS app.enableCors(); await app.listen(3000); console.log('App running on http://localhost:3000');}bootstrap();
Module
The fundamental unit — organizes related controllers, services, and providers.
import { Module } from '@nestjs/common';import { UsersController } from './users.controller';import { UsersService } from './users.service';@Module({ imports: [], // other modules this module needs controllers: [UsersController], providers: [UsersService], // services, guards, pipes, etc. exports: [UsersService], // make available to other modules})export class UsersModule {}
Controller
Handles incoming HTTP requests and returns responses.
// update-user.dto.ts — make all fields optionalimport { PartialType } from '@nestjs/mapped-types';import { CreateUserDto } from './create-user.dto';export class UpdateUserDto extends PartialType(CreateUserDto) {}
Common Validators
Decorator Validates
@IsString() string
@IsNumber() number
@IsInt() integer
@IsBoolean() boolean
@IsEmail() email format
@IsUrl() URL format
@IsUUID() UUID format
@IsDate() Date object
@IsArray() array
@IsEnum(MyEnum) enum value
@MinLength(n) string min length
@MaxLength(n) string max length
@Min(n) number min value
@Max(n) number max value
@IsOptional() field can be undefined
@IsNotEmpty() not empty string/array
@ValidateNested() validate nested object
@Type(() => NestedDto) transform nested type
Dependency Injection
How DI Works
NestJS has a built-in IoC (Inversion of Control) container.
Mark a class with @Injectable() → NestJS manages its lifecycle.
Inject via constructor — NestJS resolves dependencies automatically.
Runs before the route handler. Returns true (allow) or false (deny).
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';@Injectable()export class AuthGuard implements CanActivate { canActivate(context: ExecutionContext): boolean { const request = context.switchToHttp().getRequest(); const token = request.headers.authorization; return !!token; // simple check — use JWT in production }}// Apply to single route@Get('profile')@UseGuards(AuthGuard)getProfile() { ... }// Apply to entire controller@Controller('users')@UseGuards(AuthGuard)export class UsersController { ... }// Apply globally in main.tsapp.useGlobalGuards(new AuthGuard());
Interceptors — Transform response / logging
Wraps the route handler — runs before AND after.
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';import { Observable } from 'rxjs';import { map, tap } from 'rxjs/operators';// Wrap all responses in { data: ... }@Injectable()export class TransformInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { return next.handle().pipe( map(data => ({ data, success: true })) ); }}// Logging interceptor@Injectable()export class LoggingInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { const start = Date.now(); return next.handle().pipe( tap(() => console.log(`Request took ${Date.now() - start}ms`)) ); }}@UseInterceptors(TransformInterceptor)@Controller('users')export class UsersController { ... }
Pipes — Transform & validate input
// Built-in pipes@Get(':id')findOne(@Param('id', ParseIntPipe) id: number) { // id is guaranteed to be a number — 400 if not return this.usersService.findOne(id);}// Built-in pipes list:// ParseIntPipe, ParseFloatPipe, ParseBoolPipe,// ParseArrayPipe, ParseUUIDPipe, ParseEnumPipe,// DefaultValuePipe, ValidationPipe// Custom pipe@Injectable()export class TrimPipe implements PipeTransform { transform(value: any) { if (typeof value === 'string') return value.trim(); return value; }}
Middleware
import { Injectable, NestMiddleware } from '@nestjs/common';@Injectable()export class LoggerMiddleware implements NestMiddleware { use(req: Request, res: Response, next: () => void) { console.log(`[${req.method}] ${req.url}`); next(); }}// Apply in moduleexport class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware) .forRoutes('*'); // or specific routes/controllers }}
Exception Filters
Built-in HTTP Exceptions
import { BadRequestException, // 400 UnauthorizedException, // 401 ForbiddenException, // 403 NotFoundException, // 404 ConflictException, // 409 UnprocessableEntityException, // 422 InternalServerErrorException, // 500} from '@nestjs/common';throw new NotFoundException('User not found');throw new BadRequestException('Invalid email format');throw new ConflictException('Email already exists');
Custom Exception Filter
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';@Catch(HttpException)export class HttpExceptionFilter implements ExceptionFilter { catch(exception: HttpException, host: ArgumentsHost) { const ctx = host.switchToHttp(); const res = ctx.getResponse(); const status = exception.getStatus(); res.status(status).json({ statusCode: status, message: exception.message, timestamp: new Date().toISOString(), }); }}// Apply globallyapp.useGlobalFilters(new HttpExceptionFilter());
Configuration
@nestjs/config
npm install @nestjs/config
// app.module.tsimport { ConfigModule } from '@nestjs/config';@Module({ imports: [ ConfigModule.forRoot({ isGlobal: true, // available in all modules envFilePath: '.env', }), ],})export class AppModule {}
// Inject and useimport { ConfigService } from '@nestjs/config';@Injectable()export class AppService { constructor(private config: ConfigService) {} getDatabaseUrl() { return this.config.get<string>('DB_URL'); }}