What is Nest.js?
Nest.js is a Node.js framework for creating server-side applications. It uses modular architecture, which means that the application is divided into modules, where each module groups related functionality, so that new functionalities can be easily added without disrupting the existing parts of the application.
Nest.js uses the TypeScript language (although JavaScript can also be used), with the use of decorators (decorators are special tags that are added above the code with which we define additional instructions). In addition to HTTP, Nest.js supports WebSockets, GraphQL, gRPC and other protocols.

Modules
In Nest.js, modules are the basic building blocks of an application and serve to organize and structure the code. They group related components such as controllers, providers (services), repositories, and other modules into logical units. Modules are defined using the @Module() decorator.
Module fields:
- imports: List of modules imported into the current module. These modules are available within modules.
- controllers: List of controllers defined in the module. Controllers are responsible for handling HTTP requests.
- providers: List of providers (services) defined in the module. Providers perform business logic and can be injected into controllers or other providers.
- exports: List of providers that are exported from the module and can be used in other modules.
Creating modules
Using the Nest CLI, you can quickly and easily create modules in your Nest.js application. To create a new module, you can use the nest generate (or nest g for short) command. For example, to create a module called cats, use the following command:
|
1 |
nest generate module cats |
This command will generate a new cats.module.ts file in the src/cats directory, which looks like this:
|
1 2 3 4 |
import { Module } from '@nestjs/common'; @Module({}) export class CatsModule {} |
Example Module
Here’s how to define a simple module in Nest.js:
|
1 2 3 4 5 6 7 8 9 10 11 |
import { Module } from '@nestjs/common'; import { CatsController } from './cats.controller'; import { CatsService } from './cats.service'; @Module({ imports: [], // Ovaj modul ne uvozi nijedan drugi modul controllers: [CatsController], // Defines CatsController as the controller in this module providers: [CatsService], // Defines CatsService as a provider in this module exports: [CatsService], // Eksplicitno eksportuje CatsService za upotrebu u drugim modulima }) export class CatsModule {} |
Main Module (App Module)
Every Nest.js application has a main module, usually named AppModule, which is the root module of the application. It can import other modules and serves as an entry point for the application.
|
1 2 3 4 5 6 7 |
import { Module } from '@nestjs/common'; import { CatsModule } from './cats/cats.module'; @Module({ imports: [CatsModule], // Uvozi CatsModule u glavni modul }) export class AppModule {} |
Controllers
Controllers are responsible for handling HTTP requests and returning appropriate responses to clients. In Nest.js, controllers act as intermediaries between the client and the service. They define routes and methods that are fired when certain routes are hit. Controllers are marked with the @Controller() decorator. Decorators are also used within the controller itself to mark specific methods or routes such as: @Get(), @Post(), @Put(), @Delete(), etc.
|
1 2 3 4 5 6 7 8 9 |
import { Controller, Get } from '@nestjs/common'; @Controller('cats') export class CatsController { @Get() findAll(): string { return 'This action returns all cats'; } } |
In this example, CatsController has a single route that responds to GET requests to /cats and returns the string ‘This action returns all cats’.
Adding a controller to a module
After creating a controller or service, you need to update the module to include them:
Example
|
1 2 3 4 5 6 7 8 |
import { Module } from '@nestjs/common'; import { CatsController } from './cats.controller'; @Module({ controllers: [CatsController], providers: [], }) export class CatsModule {} |
Services
Services represent the application layer that performs business logic and provides data to controllers. Services usually contain methods that are used to perform various tasks such as accessing a database, working with third-party APIs, processing data, etc.
- Defining services: Services are defined as classes and typically use the @Injectable() decorator so that Nest.js can inject them into controllers or otherservices.
- Dependency injection: Services can be injected into controllers or other services using a constructor.
Creating services
To add a new service, use the nest generate service (or nest g service for short) command.
|
1 |
nest generate service cats |
This command will generate a cats.service.ts file in the src/cats directory with the base implementation of the service:
|
1 2 3 4 5 6 7 8 9 10 |
import { Injectable } from '@nestjs/common'; @Injectable() export class CatsService { private readonly cats = ['Cat1', 'Cat2']; findAll(): string[] { return this.cats; } } |
Adding services to the module
When you create a new service using the nest generate service cats command, the Nest CLI will automatically update the corresponding module to include the newly created service in the providers field of that module. This allows the service to be available within the module without additional manual intervention.
|
1 2 3 4 5 6 7 8 9 |
import { Module } from '@nestjs/common'; import { CatsController } from './cats.controller'; import { CatsService } from './cats.service'; @Module({ controllers: [CatsController], providers: [CatsService], }) export class CatsModule {} |
Example:
|
1 2 3 4 5 6 7 8 9 10 |
import { Injectable } from '@nestjs/common'; @Injectable() export class UsersService { private readonly users = ['User1', 'User2']; findAll(): string[] { return this.users; } } |
In this example, UsersService has a method findAll that returns a list of users.
Middleware
Middleware are functions that are executed during HTTP request processing. Middleware operates in the space between receiving a request and returning a response, allowing modification of the request and response, redirecting the flow, or terminating the request. In Nest.js, middleware is defined as classes that implement the NestMiddleware interface or as plain functions. Middleware can be applied to specific routes or globally to all routes.
Creating middleware
To add new middleware, use the nest generate middleware (or nest g middleware for short) command.
|
1 |
nest generate middleware logger |
This command will generate a logger.middleware.ts file in the src directory with the base middleware implementation:
|
1 2 3 4 5 6 7 8 9 10 |
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(); } } |
In this example, LoggerMiddleware logs every request that goes through the application.
Registering middleware in the module:
To register the middleware, you need to add it to the appropriate module.
12345678910
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common'; @Module({})export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware) .forRoutes('users'); }}
In this example,
LoggerMiddleware will be applied to all requests going to the
/users route.
|
1 2 3 4 5 6 7 8 9 10 |
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common'; @Module({}) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware) .forRoutes('users'); } } |
Providers
Nest.js has a built-in dependency injection mechanism, which facilitates dependency management and code testing. This means that components such as services can be automatically injected where they are needed, instead of explicitly instantiating them. Read more about dependency injection in the article “What is Dependency Injection”.
Providers are a core concept in Nest.js that enable the creation and sharing of object instances. They are usually used to implement services, but they can also include any class that you want to be available through the Dependency Injection (DI) mechanism ie. providers can be services, repositories, factory functions, etc.
Provider tracking
Providers are marked as classes that use the @Injectable() decorator.
Example
|
1 2 3 4 5 6 7 8 9 10 |
import { Injectable } from '@nestjs/common'; @Injectable() export class CatsService { private readonly cats = ['Cat1', 'Cat2', 'Cat3']; findAll(): string[] { return this.cats; } } |
In this example, CatsService is a provider defined with the @Injectable() decorator.
Provider registration
|
1 2 3 4 5 6 7 8 9 |
import { Module } from '@nestjs/common'; import { CatsController } from './cats.controller'; import { CatsService } from './cats.service'; @Module({ controllers: [CatsController], providers: [CatsService], }) export class CatsModule {} |
In this example, CatsService is registered as a provider in CatsModule.
Provider injection
Providers can be injected into other classes or providers using a constructor.
Example
|
1 2 3 4 5 6 7 8 9 10 11 12 |
import { Controller, Get } from '@nestjs/common'; import { CatsService } from './cats.service'; @Controller('cats') export class CatsController { constructor(private readonly catsService: CatsService) {} @Get() findAll(): string[] { return this.catsService.findAll(); } } |
Repository
Repositories are a layer that enables data access and management. They abstract the interaction with the database or any other data source, thus allowing for cleaner and more consistent code. They are used to perform CRUD operations (create, read, update, delete).
Example
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { User } from './user.entity'; @Injectable() export class UserRepository { constructor( @InjectRepository(User) private readonly userRepository: Repository<User>, ) {} findAll(): Promise<User[]> { return this.userRepository.find(); } findOne(id: number): Promise<User> { return this.userRepository.findOneBy({ id }); } create(user: User): Promise<User> { return this.userRepository.save(user); } async remove(id: number): Promise<void> { await this.userRepository.delete(id); } } |
In this example, the UserRepository uses a TypeORM repository to interact with the User entity.
Exception Filters
Exception filters are like safety nets in a program that catch errors and decide what to do with them. When something unexpected happens in the application, exception filters allow errors to be caught and an appropriate message returned to the user. Exception filters allow us to handle errors centrally and eliminate the need to write actions that would handle errors all over the code.
Step 1: Defining the Exception Filter
We define the filter using the @Catch() decorator. This is a simple example of a filter that catches all errors:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common'; import { Request, Response } from 'express'; @Catch() export class AllExceptionsFilter implements ExceptionFilter { catch(exception: unknown, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse<Response>(); const request = ctx.getRequest<Request>(); const status = exception instanceof HttpException ? exception.getStatus() : 500; response.status(status).json({ statusCode: status, timestamp: new Date().toISOString(), path: request.url, }); } } |
Step 2: Using the Exception Filter
We can apply the filter at different levels: globally (for the entire application), at the controller level or at the level of individual routes.
Application at the Global Level
To make the filter work for the whole application, we will add it to the main file of the application (eg main.ts):
|
1 2 3 4 5 6 7 8 9 10 |
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { AllExceptionsFilter } from './all-exceptions.filter'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalFilters(new AllExceptionsFilter()); await app.listen(3000); } bootstrap(); |
Application at the Controller Level
To make the filter work only for a specific controller, we add the @UseFilters decorator above the controller:
|
1 2 3 4 5 6 7 8 9 10 11 |
import { Controller, Get, UseFilters } from '@nestjs/common'; import { AllExceptionsFilter } from './all-exceptions.filter'; @Controller('cats') @UseFilters(AllExceptionsFilter) export class CatsController { @Get() findAll() { throw new Error('This is an error'); } } |
Application at the Level of Individual Routes
To make the filter work only for a specific route, we add the @UseFilters decorator above that route:
|
1 2 3 4 5 6 7 8 9 10 11 |
import { Controller, Get, UseFilters } from '@nestjs.common'; import { AllExceptionsFilter } from './all-exceptions.filter'; @Controller('cats') export class CatsController { @Get() @UseFilters(AllExceptionsFilter) findAll() { throw new Error('This is an error'); } } |
Pipes
Pipes in Nest.js are a powerful tool that enables data transformation and validation. Pipes can transform incoming data, verify it, and even discard invalid data. This allows the transformation and validation logic to be centralized and easily reused.
Creating Pipes
A pipe is created by implementing the PipeTransform interface and defining a transform method that will process the data.
Example
In this example a pipe that converts the incoming string to a number:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
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; } } |
Using Pipes
You can deploy Pipes at the global level, the controller level, or the route level.
Global Application
Global implementation of pipes is used in the main application file (eg main.ts):
|
1 2 3 4 5 6 7 8 9 10 |
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); await app.listen(3000); } bootstrap(); |
Application on Controller
You can apply pipes to a specific controller using the @UsePipes() decorator:
|
1 2 3 4 5 6 7 8 9 10 11 |
import { Controller, Get, UsePipes, Param } from '@nestjs.common'; import { ParseIntPipe } from './parse-int.pipe'; @Controller('cats') export class CatsController { @Get(':id') @UsePipes(ParseIntPipe) findOne(@Param('id') id: number) { return `This action returns a cat with id ${id}`; } } |
Application to Route
You can apply pipes to a specific route:
|
1 2 3 4 5 6 7 8 9 10 |
import { Controller, Get, Param } from '@nestjs.common'; import { ParseIntPipe } from './parse-int.pipe'; @Controller('cats') export class CatsController { @Get(':id') findOne(@Param('id', ParseIntPipe) id: number) { return `This action returns a cat with id ${id}`; } } |

