In NestJS, each module has its own execution context, meaning each module has its own dependency injection container. When a module is lazily loaded, its execution context is created at the time of loading.
The execution context refers to how each request is processed during the runtime of a NestJS application. Understanding the execution context is crucial for debugging and optimizing applications.
Request-Response Cycle
In NestJS, every HTTP request goes through a series of processing steps that make up the request-response cycle. Below are the main components of the request-response cycle:
Interceptors
- Executed before or after route handlers.
- Can modify request or response objects.
- Useful for common operations like logging or performance monitoring.
Guards
- Executed before route handlers.
- Used to protect routes, such as verifying user permissions.
- If a guard returns
falseor throws an exception, the request is interrupted.
Pipes
- Executed before route handlers.
- Used to transform incoming data.
- Can validate or convert data formats.
Exception Filters
- Triggered when a component throws an exception.
- Used for globally handling exceptions, such as standardizing error response formats.
Middlewares
- Executed before the request reaches the controller.
- Used for preprocessing tasks like authentication or logging.
Example Code
Below is a simple example demonstrating how to use NestJS execution context components:
Creating a Simple Controller
// app.controller.ts
import { Controller, Get } from '@nestjs/common';
@Controller()
export class AppController {
@Get()
getHello(): string {
return 'Hello World!';
}
}Adding a Guard
// auth.guard.ts
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
// Check user authentication status here
return true; // Assume the user is authenticated
}
}Using the Guard
// app.controller.ts
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from './auth.guard';
@Controller()
export class AppController {
@Get()
@UseGuards(AuthGuard)
getHello(): string {
return 'Hello World!';
}
}Adding an Interceptor
// logging.interceptor.ts
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } 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
// app.controller.ts
import { Controller, Get, UseGuards, UseInterceptors } from '@nestjs/common';
import { AuthGuard } from './auth.guard';
import { LoggingInterceptor } from './logging.interceptor';
@Controller()
export class AppController {
@Get()
@UseGuards(AuthGuard)
@UseInterceptors(LoggingInterceptor)
getHello(): string {
return 'Hello World!';
}
}Adding an Exception Filter
// global.exception.filter.ts
import { ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus } from '@nestjs/common';
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
});
}
}Configuring the Exception Filter
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { AllExceptionsFilter } from './global.exception.filter';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new AllExceptionsFilter());
await app.listen(3000);
}
bootstrap();Shared Services
To share a service across multiple modules, you can provide the service in the main module.
// main.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { RouterModule } from '@nestjs/core';
import { SharedService } from './shared.service';
@Module({
imports: [
RouterModule.register([
{ path: 'users', module: () => import('./users/users.module').then(m => m.UsersModule) },
{ path: 'posts', module: () => import('./posts/posts.module').then(m => m.PostsModule) },
]),
],
controllers: [AppController],
providers: [AppService, SharedService], // Provide the shared service here
})
export class MainModule {}
// shared.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class SharedService {
someMethod() {
return 'Shared service method';
}
}
// users.controller.ts
import { Controller, Get } from '@nestjs/common';
import { SharedService } from '../shared.service';
@Controller('users')
export class UsersController {
constructor(private readonly sharedService: SharedService) {}
@Get('shared')
getSharedData() {
return this.sharedService.someMethod();
}
}
// posts.controller.ts
import { Controller, Get } from '@nestjs/common';
import { SharedService } from '../shared.service';
@Controller('posts')
export class PostsController {
constructor(private readonly sharedService: SharedService) {}
@Get('shared')
getSharedData() {
return this.sharedService.someMethod();
}
}



