- Modules
- Controllers
- Providers (services)
- Dependency Injection
- Middleware
- Pipes
- Guards
- Exception Filters
- Interceptors
These are the core concepts of NestJS, a progressive Node.js framwork designed for building efficient and scalable server-side applications. Let learn each core concept with code example.

Request lifecycle (per request)
- Platform middleware → Nest middleware
- Express/Fastify middleware first, then Nest
@Injectable()middleware - Errors here are not caught by Nest exception filters.
- Express/Fastify middleware first, then Nest
- Guards
- Global guards → controller-scoped guards → route-scoped guards.
- Can block the request or throw.
- Interceptors (pre)
- Global → controller → route.
- Wrap the handler; can change execution context or short-circuit.
- Pipes
- Global pipes → controller pipes → route pipes → parameter-level pipes.
- Validate/transform inputs before handler runs.
- Controller handler
- Route method executes; typically calls providers via DI.
- Providers (services)
- Your business logic executes; may access repositories, etc.
- Interceptors (post)
- The same interceptors continue after the handler, transforming or tapping the response/error stream.
- Exception filters
- If an exception is thrown from guards/pipes/handler/interceptors:
- Method-scoped filter → controller-scoped filter → global filter.
- Format and return the error response.
- Note: middleware exceptions aren’t caught by Nest filters.
- If an exception is thrown from guards/pipes/handler/interceptors:
- Response is sent to the client.
1. Modules
Modules organize related functionality (controllers, services) and provide structure.
// cats.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {}
This module wires together the CatsController (handles requests) and CatsService (business logic).
2. Controllers
Controllers handle HTTP requests and send responses.
// cats.controller.ts
import { Controller, Get, Post, Body, Param } from '@nestjs/common';
import { CatsService } from './cats.service';
import { CreateCatDto } from './dto/create-cat.dto';
@Controller('cats')
export class CatsController {
constructor(private readonly catsService: CatsService) {}
@Get()
findAll() {
return this.catsService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.catsService.findOne(+id);
}
@Post()
create(@Body() createCatDto: CreateCatDto) {
return this.catsService.create(createCatDto);
}
}
This controller exposes /cats GET and POST routes. Incoming requests are dispatched to methods, which delegate logic to the service.
3. Providers (Services)
Providers encapsulate business logic and can be injected into other classes (like controllers).
// cats.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { CreateCatDto } from './dto/create-cat.dto';
@Injectable()
export class CatsService {
private cats = [];
create(dto: CreateCatDto) {
const newCat = { id: Date.now(), ...dto };
this.cats.push(newCat);
return newCat;
}
findAll() {
return this.cats;
}
findOne(id: number) {
const cat = this.cats.find(c => c.id === id);
if (!cat) throw new NotFoundException(`Cat #${id} not found`);
return cat;
}
}
The service persists and retrieves data and is injected into the controller above.
4. Dependency Injection
Dependency Injection lets you inject providers into other components, keeping code modular and testable.
// The CatsService is injected into the CatsController via constructor:
constructor(private readonly catsService: CatsService) {}
When the controller is initialized, NestJS provides an instance of CatsService automatically.
5. Middleware
Middleware execute before controllers and are used for cross-cutting concerns like logging or authentication.
// logger.middleware.ts
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();
}
}
You apply it to routes or globally via your module’s configure method.
6. Pipes
Pipes transform or validate incoming data before it reaches your code.
// main.ts
import { ValidationPipe } from '@nestjs/common';
app.useGlobalPipes(new ValidationPipe());
// create-cat.dto.ts
import { IsString, IsNumber } from 'class-validator';
export class CreateCatDto {
@IsString()
name: string;
@IsNumber()
age: number;
}
Incoming POST bodies to /cats are automatically validated. If the data is invalid, NestJS responds with an error.
7. Guards
Guards control access, for example, authenticating users.
// auth.guard.ts
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const req = context.switchToHttp().getRequest();
if (!req.headers.authorization) {
throw new UnauthorizedException();
}
// logic for validity
return true;
}
}
Apply a guard using @UseGuards(AuthGuard) above a controller or route.
8. Exception Filters
Exception Filters catch errors and format responses.
// http-error.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
@Catch(HttpException)
export class HttpErrorFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const status = exception.getStatus();
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
path: ctx.getRequest().url,
});
}
}
This filter centralizes error responses for structured client feedback.
9. Interceptors
Interceptors can transform responses, log, or cache results.
// response.interceptor.ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable()
export class ResponseInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(map(data => ({ success: true, data })));
}
}
Apply globally or per-controller to wrap every successful response in a consistent envelope.
These code examples showcase how each NestJS concept is applied in actual backend development, with modular organization, clear separation of concerns, and extensible architecture.