Introduction
NestJS is a progressive Node.js framework for building efficient and scalable server-side applications. It leverages TypeScript and combines elements of Object-Oriented Programming (OOP), Functional Programming (FP), and Functional Reactive Programming (FRP). By using a modular approach, decorators, and dependency injection, NestJS introduces a level of structure that’s suited for both small-scale projects and large enterprise applications. This guide covers the core concepts you need to master NestJS development.
Modules
In NestJS, everything starts with modules. They are a way of organizing code within an application and encapsulating providers which can be shared across the application. Let’s define a basic module with a single controller.
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
@Module({
controllers: [AppController],
})
export class AppModule {}
Controllers
Controllers handle incoming requests and return responses. They are decorated with @Controller()
, and the routes are configured with decorators such as @Get()
, @Post()
, etc.
import { Controller, Get } from '@nestjs/common';
@Controller('cats')
export class CatsController {
@Get()
findAll(): string {
return 'This action returns all cats';
}
}
Providers and Services
Providers can be injected into controllers or other providers, encapsulating the business logic. Services are common providers and are usually decorated with @Injectable()
.
import { Injectable } from '@nestjs/common';
@Injectable()
export class CatsService {
private readonly cats: Cat[] = [];
create(cat: Cat) {
this.cats.push(cat);
}
findAll(): Cat[] {
return this.cats;
}
}
Dependency Injection
NestJS makes heavy use of dependency injection which is key to creating scalable systems. Services can be injected into controllers by simply adding a constructor parameter with a type.
import { Controller, Get } from '@nestjs/common';
import { CatsService } from './cats.service';
@Controller('cats')
export class CatsController {
constructor(private catsService: CatsService) { }
@Get()
findAll() {
return this.catsService.findAll();
}
}
Pipes
Pipes are NestJS components that can transform or validate request data. Use them as an argument decorator, such as @Body()
combined with @Post()
for data transformation and validation.
import { Body, Controller, Post } from '@nestjs/common';
import { CreateCatDto } from './create-cat.dto';
import { CatsService } from './cats.service';
@Controller('cats')
export class CatsController {
constructor(private catsService: CatsService) { }
@Post()
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
}
Guards
Guards are responsible for whether a certain request will be handled by the route handler or not, often used for authentication.
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class CatsGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise | Observable {
const request = context.switchToHttp().getRequest();
return validateRequest(request);
}
}
Interceptors
Interceptors allow you to intercept the request/response stream, adding extra logic like logging, transformation, or response mapping.
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 {
console.log('Before...');
const now = Date.now();
return next
.handle()
.pipe(
tap(() => console.log(`After... ${Date.now() - now}ms`)),
);
}
}
Decorators
Custom decorators in NestJS allow you to define and attach new behavior to method parameters or class properties. They are widely used to enrich the context information, like extracting a user id from an authorization token.
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const GetUser = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
},
);
Microservices
NestJS supports building scalable microservices with different transport layers like TCP, Redis, MQTT, and more.
import { Controller } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';
@Controller()
export class MathController {
@MessagePattern('add')
add(data: number[]): number {
return (data || []).reduce((a, b) => a + b);
}
}
WebSocket
Real-time bi-directional communication is easy with NestJS using WebSocket gateways.
import { SubscribeMessage, WebSocketGateway, WebSocketServer } from '@nestjs/websockets';
import { Server } from 'socket.io';
@WebSocketGateway()
export class ChatGateway {
@WebSocketServer()
server: Server;
@SubscribeMessage('message')
handleMessage(client: any, payload: any): void {
this.server.emit('message', payload);
}
}
Conclusion
This guide touched upon the core concepts of NestJS: modules, controllers, providers, dependency injection, pipes, guards, interceptors, decorators, microservices, and websockets. Armed with these fundamentals, you’re well-equipped to delve deeper into NestJS and harness its power to build efficient and scalable server-side solutions.