NestJS is a progressive Node.js framework for building efficient, reliable, and scalable server-side applications. It uses modern JavaScript, is built with TypeScript, and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming).
Table of Contents
- Introduction to NestJS
- Setting Up a NestJS Project
- Controllers
- Providers and Services
- Modules
- Middleware
- Exception Filters
- Pipes
- Guards
- Interceptors
- Database Integration
- Authentication
- Deployment
Introduction to NestJS
NestJS provides an out-of-the-box application architecture that allows developers to create highly testable, scalable, loosely coupled, and easily maintainable applications. It’s heavily inspired by Angular’s architecture.
Key features:
- Built with TypeScript (supports JavaScript)
- Modular architecture
- Dependency injection
- Microservices support
- WebSockets support
- Easy integration with databases (TypeORM, Sequelize, Mongoose)
- Extensive documentation
Setting Up a NestJS Project
To get started, you’ll need Node.js (v12 or newer) installed. Then install the Nest CLI:
npm i -g @nestjs/cliCreate a new project:
nest new project-nameNavigate to the project directory and start the development server:
cd project-name
npm run start:devThis will start the server on http://localhost:3000 with hot-reload enabled.
Controllers
Controllers handle incoming requests and return responses to the client. They’re defined using the @Controller() decorator.
Example controller:
import { Controller, Get, Post, Body, Param } from '@nestjs/common';
@Controller('cats')
export class CatsController { @Get() findAll(): string { return 'This action returns all cats'; } @Post() create(@Body() cat: any): string { return 'This action adds a new cat'; } @Get(':id') findOne(@Param('id') id: string): string { return `This action returns a #${id} cat`; }
}Providers and Services
Providers are a fundamental concept in Nest. Many basic Nest classes may be treated as providers – services, repositories, factories, helpers, etc.
Example service:
import { Injectable } from '@nestjs/common';
@Injectable()
export class CatsService { private readonly cats: any[] = []; create(cat: any) { this.cats.push(cat); } findAll(): any[] { return this.cats; } findOne(id: number): any { return this.cats.find(cat => cat.id === id); }
}To use the service in a controller:
@Controller('cats')
export class CatsController { constructor(private readonly catsService: CatsService) {} @Post() async create(@Body() cat: any) { this.catsService.create(cat); } @Get() async findAll(): Promise<any[]> { return this.catsService.findAll(); }
}Modules
Modules are used to organize the application structure. The @Module() decorator provides metadata that Nest uses to organize the application structure.
Example module:
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({ controllers: [CatsController], providers: [CatsService],
})
export class CatsModule {}Middleware
Middleware are functions that have access to the request and response objects. They can:
- Execute any code
- Make changes to the request and response objects
- End the request-response cycle
- Call the next middleware in the stack
Example middleware:
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) { console.log('Request...'); next(); }
}Applying middleware:
export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware) .forRoutes('cats'); }
}Exception Filters
Nest comes with a built-in exceptions layer that handles all unhandled exceptions. You can also create custom exception filters.
Example custom filter:
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Request, Response } from 'express';
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter { catch(exception: HttpException, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse<Response>(); const request = ctx.getRequest<Request>(); const status = exception.getStatus(); response .status(status) .json({ statusCode: status, timestamp: new Date().toISOString(), path: request.url, }); }
}Using the filter:
@Post()
@UseFilters(new HttpExceptionFilter())
async create(@Body() cat: any) { throw new ForbiddenException();
}Pipes
Pipes operate on the arguments being processed by a route handler. They can perform data transformation or validation.
Built-in pipes include:
ValidationPipeParseIntPipeParseBoolPipeParseArrayPipeParseUUIDPipeDefaultValuePipe
Example usage:
@Get(':id')
async findOne( @Param('id', ParseIntPipe) id: number,
) { return this.catsService.findOne(id);
}Custom pipe example:
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
@Injectable()
export class ParseIntPipe implements PipeTransform<string, number> { transform(value: string, metadata: ArgumentMetadata): number { const val = parseInt(value, 10); if (isNaN(val)) { throw new BadRequestException('Validation failed'); } return val; }
}Guards
Guards determine whether a request will be handled by the route handler or not. They’re executed after middleware but before pipes.
Example guard:
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class AuthGuard implements CanActivate { canActivate( context: ExecutionContext, ): boolean | Promise<boolean> | Observable<boolean> { const request = context.switchToHttp().getRequest(); return validateRequest(request); }
}Using the guard:
@Controller('cats')
@UseGuards(AuthGuard)
export class CatsController {}Interceptors
Interceptors can:
- Bind extra logic before/after method execution
- Transform the result returned from a function
- Transform the exception thrown from a function
- Extend the basic function behavior
Example interceptor:
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class LoggingInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { console.log('Before...'); const now = Date.now(); return next .handle() .pipe( tap(() => console.log(`After... ${Date.now() - now}ms`)), ); }
}Using the interceptor:
@UseInterceptors(LoggingInterceptor)
export class CatsController {}Database Integration
Nest works well with many databases. Here’s an example with TypeORM:
- Install required packages:
npm install @nestjs/typeorm typeorm mysql2- Configure the module:
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({ imports: [ TypeOrmModule.forRoot({ type: 'mysql', host: 'localhost', port: 3306, username: 'root', password: 'root', database: 'test', entities: [], synchronize: true, }), ],
})
export class AppModule {}- Define an entity:
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class Cat { @PrimaryGeneratedColumn() id: number; @Column() name: string; @Column() age: number; @Column() breed: string;
}- Create a repository:
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Cat } from './cat.entity';
@Injectable()
export class CatsService { constructor( @InjectRepository(Cat) private catsRepository: Repository<Cat>, ) {} findAll(): Promise<Cat[]> { return this.catsRepository.find(); } findOne(id: number): Promise<Cat> { return this.catsRepository.findOne({ where: { id } }); } async remove(id: number): Promise<void> { await this.catsRepository.delete(id); }
}Authentication
Here’s a basic JWT authentication example:
- Install required packages:
npm install @nestjs/passport passport passport-jwt @nestjs/jwt
npm install @types/passport-jwt --save-dev- Create an auth module:
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { AuthService } from './auth.service';
import { JwtStrategy } from './jwt.strategy';
import { UsersModule } from '../users/users.module';
@Module({ imports: [ UsersModule, JwtModule.register({ secret: 'secretKey', signOptions: { expiresIn: '60s' }, }), ], providers: [AuthService, JwtStrategy], exports: [AuthService],
})
export class AuthModule {}- Create a JWT strategy:
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) { constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKey: 'secretKey', }); } async validate(payload: any) { return { userId: payload.sub, username: payload.username }; }
}- Create an auth service:
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class AuthService { constructor(private jwtService: JwtService) {} async validateUser(username: string, pass: string): Promise<any> { // Validate user against database const user = await this.usersService.findOne(username); if (user && user.password === pass) { const { password, ...result } = user; return result; } return null; } async login(user: any) { const payload = { username: user.username, sub: user.userId }; return { access_token: this.jwtService.sign(payload), }; }
}- Protect routes with guards:
@UseGuards(AuthGuard('jwt'))
@Controller('profile')
export class ProfileController { @Get() getProfile(@Request() req) { return req.user; }
}Deployment
To deploy a NestJS application:
- Build the application:
npm run build- Run the production server:
node dist/mainFor production, consider:
- Using environment variables
- Setting up a process manager (PM2, systemd)
- Implementing proper logging
- Setting up monitoring
- Using a reverse proxy (Nginx, Apache)
Example PM2 configuration:
npm install pm2 -g
pm2 start dist/main.js --name "nest-app"
pm2 save
pm2 startupConclusion
NestJS provides a robust framework for building server-side applications with Node.js. Its modular architecture, extensive features, and TypeScript support make it an excellent choice for enterprise-grade applications. This handbook covered the core concepts, but NestJS has much more to offer including microservices, GraphQL support, WebSockets, and more.
Happy coding with NestJS!


