The NestJS Handbook – Learn to Use Nest with Code Examples

The NestJS Handbook – Learn to Use Nest with Code Examples

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

  1. Introduction to NestJS
  2. Setting Up a NestJS Project
  3. Controllers
  4. Providers and Services
  5. Modules
  6. Middleware
  7. Exception Filters
  8. Pipes
  9. Guards
  10. Interceptors
  11. Database Integration
  12. Authentication
  13. 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/cli

Create a new project:

nest new project-name

Navigate to the project directory and start the development server:

cd project-name
npm run start:dev

This 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:

  • ValidationPipe
  • ParseIntPipe
  • ParseBoolPipe
  • ParseArrayPipe
  • ParseUUIDPipe
  • DefaultValuePipe

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:

  1. Install required packages:
npm install @nestjs/typeorm typeorm mysql2
  1. 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 {}
  1. 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;
}
  1. 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:

  1. Install required packages:
npm install @nestjs/passport passport passport-jwt @nestjs/jwt
npm install @types/passport-jwt --save-dev
  1. 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 {}
  1. 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 }; }
}
  1. 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), }; }
}
  1. 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:

  1. Build the application:
npm run build
  1. Run the production server:
node dist/main

For 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 startup

Conclusion

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!

Instagram Feed

This error message is only visible to WordPress admins

Error: No feed found.

Please go to the Instagram Feed settings page to create a feed.

Comments

No comments yet. Why don’t you start the discussion?

    Leave a Reply

    Your email address will not be published. Required fields are marked *