In-Depth Explanation of Guards, Interceptors, and Filters in NestJS

  • 1081Words
  • 5Minutes
  • 11 Jul, 2024

NestJS provides many powerful tools and features to manage and handle requests. In this article, we will delve into guards, interceptors, and filters in NestJS, demonstrating their usage with examples in real-world scenarios. We will also explain the differences between them and middleware through case studies.

Guards

Concept of Guards

Guards are mechanisms used to control the flow of requests, allowing verification and checks before the request reaches the handler. They are typically used to implement authentication and authorization logic.

Creating a Guard

To create a guard, you need to implement the CanActivate interface and define the canActivate method. Here is a simple authentication guard example:

1
import { Injectable, CanActivate, ExecutionContext } from "@nestjs/common";
2
import { Observable } from "rxjs";
3
4
@Injectable()
5
export class AuthGuard implements CanActivate {
6
canActivate(
7
context: ExecutionContext,
8
): boolean | Promise<boolean> | Observable<boolean> {
9
const request = context.switchToHttp().getRequest();
10
return this.validateRequest(request);
11
}
12
13
validateRequest(request: any): boolean {
14
// Validation logic
15
return request.headers.authorization === "my-secret-token"; // Simple example
16
}
17
}

Applying Guards

Guards can be applied at the controller or method level using the @UseGuards decorator:

Controller Level
1
import { Controller, Get, UseGuards } from "@nestjs/common";
2
import { AuthGuard } from "./auth.guard";
3
4
@Controller("cats")
5
@UseGuards(AuthGuard)
6
export class CatsController {
7
@Get()
8
findAll() {
9
return "This action returns all cats";
10
}
11
}
Method Level
1
import { Controller, Get, UseGuards } from "@nestjs/common";
2
import { AuthGuard } from "./auth.guard";
3
4
@Controller("cats")
5
export class CatsController {
6
@Get()
7
@UseGuards(AuthGuard)
8
findAll() {
9
return "This action returns all cats";
10
}
11
}
Global Level
1
import { NestFactory } from "@nestjs/core";
2
import { AppModule } from "./app.module";
3
import { AuthGuard } from "./auth.guard";
4
5
async function bootstrap() {
6
const app = await NestFactory.create(AppModule);
7
app.useGlobalGuards(new AuthGuard());
8
await app.listen(3000);
9
}
10
bootstrap();

Real-World Use Cases for Guards

Guards are often used to protect routes that require authentication or authorization. For example, you can use a guard to verify a user’s identity on a route that requires login.

Interceptors

Concept of Interceptors

Interceptors are mechanisms that can execute custom logic before and after request handling. They are often used for logging, transforming response data, and handling exceptions.

Creating an Interceptor

To create an interceptor, you need to implement the NestInterceptor interface and define the intercept method. Here is an example of a response data formatting interceptor:

1
import {
2
Injectable,
3
NestInterceptor,
4
ExecutionContext,
5
CallHandler,
6
} from "@nestjs/common";
7
import { Observable } from "rxjs";
8
import { map } from "rxjs/operators";
9
10
@Injectable()
11
export class TransformInterceptor implements NestInterceptor {
12
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
13
return next.handle().pipe(map((data) => ({ data })));
14
}
15
}

Applying Interceptors

Interceptors can be applied at the controller or method level using the @UseInterceptors decorator:

Controller Level
1
import { Controller, Get, UseInterceptors } from "@nestjs/common";
2
import { TransformInterceptor } from "./transform.interceptor";
3
4
@Controller("cats")
5
@UseInterceptors(TransformInterceptor)
6
export class CatsController {
7
@Get()
8
findAll() {
9
return [{ name: "Tom" }, { name: "Jerry" }];
10
}
11
}
Method Level
1
import { Controller, Get, UseInterceptors } from "@nestjs/common";
2
import { TransformInterceptor } from "./transform.interceptor";
3
4
@Controller("cats")
5
export class CatsController {
6
@Get()
7
@UseInterceptors(TransformInterceptor)
8
findAll() {
9
return [{ name: "Tom" }, { name: "Jerry" }];
10
}
11
}
Global Level
1
import { NestFactory } from "@nestjs/core";
2
import { AppModule } from "./app.module";
3
import { TransformInterceptor } from "./transform.interceptor";
4
5
async function bootstrap() {
6
const app = await NestFactory.create(AppModule);
7
app.useGlobalInterceptors(new TransformInterceptor());
8
await app.listen(3000);
9
}
10
bootstrap();

Real-World Use Cases for Interceptors

Interceptors are commonly used for logging, transforming response data, and handling exceptions. For example, you can use an interceptor to format response data uniformly on a route.

Filters

Concept of Filters

Filters are mechanisms used to capture and handle unhandled exceptions. They can capture any unhandled exception in controllers and execute custom error handling logic.

Creating a Filter

To create a filter, you need to implement the ExceptionFilter interface and define the catch method. Here is an example of a global exception filter:

1
import {
2
ExceptionFilter,
3
Catch,
4
ArgumentsHost,
5
HttpException,
6
} from "@nestjs/common";
7
import { Request, Response } from "express";
8
9
@Catch(HttpException)
10
export class HttpExceptionFilter implements ExceptionFilter {
11
catch(exception: HttpException, host: ArgumentsHost) {
12
const ctx = host.switchToHttp();
13
const response = ctx.getResponse<Response>();
14
const request = ctx.getRequest<Request>();
15
const status = exception.getStatus();
16
17
response.status(status).json({
18
statusCode: status,
19
timestamp: new Date().toISOString(),
20
path: request.url,
21
});
22
}
23
}

Applying Filters

Filters can be applied at the controller or method level using the @UseFilters decorator or globally:

Controller Level
1
import { Controller, Get, UseFilters } from "@nestjs.common";
2
import { HttpExceptionFilter } from "./http-exception.filter";
3
4
@Controller("cats")
5
@UseFilters(HttpExceptionFilter)
6
export class CatsController {
7
@Get()
8
findAll() {
9
throw new HttpException("Forbidden", 403);
10
}
11
}
Method Level
1
import { Controller, Get, UseFilters } from "@nestjs.common";
2
import { HttpExceptionFilter } from "./http-exception.filter";
3
4
@Controller("cats")
5
export class CatsController {
6
@Get()
7
@UseFilters(HttpExceptionFilter)
8
findAll() {
9
throw new HttpException("Forbidden", 403);
10
}
11
}
Global Level
1
import { NestFactory } from "@nestjs/core";
2
import { AppModule } from "./app.module";
3
import { HttpExceptionFilter } from "./http-exception.filter";
4
5
async function bootstrap() {
6
const app = await NestFactory.create(AppModule);
7
app.useGlobalFilters(new HttpExceptionFilter());
8
await app.listen(3000);
9
}
10
bootstrap();

Real-World Use Cases for Filters

Filters are commonly used for global error handling. For example, you can create a global exception filter to handle all unhandled exceptions and return consistent error responses.

Middleware

Concept of Middleware

Middleware are functions executed before the request reaches the route handler and before the response is sent to the client. Middleware can handle requests, modify responses, end the request-response cycle, or call the next middleware function.

Creating Middleware

To create middleware, you need to implement the NestMiddleware interface and define the use method. Here is a simple logging middleware example:

1
import { Injectable, NestMiddleware } from "@nestjs/common";
2
import { Request, Response, NextFunction } from "express";
3
4
@Injectable()
5
export class LoggerMiddleware implements NestMiddleware {
6
use(req: Request, res: Response, next: NextFunction) {
7
console.log(`Request...`);
8
next();
9
}
10
}

Applying Middleware

Middleware can be applied in a module using the forRoutes method:

1
import { Module, NestModule, MiddlewareConsumer } from "@nestjs.common";
2
import { CatsController } from "./cats.controller";
3
import { LoggerMiddleware } from "./logger.middleware";
4
5
@Module({
6
controllers: [CatsController],
7
})
8
export class CatsModule implements NestModule {
9
configure(consumer: MiddlewareConsumer) {
10
consumer.apply(LoggerMiddleware).forRoutes(CatsController);
11
}
12
}

Real-World Use Cases for Middleware

Middleware are commonly used for logging, request validation, and response compression. For example, you can use middleware to log request information in a module that requires logging for all requests.

Differences Between Guards, Interceptors, Filters, and Middleware

  • Guards: Used to control whether a request can proceed, commonly for authentication and authorization.
  • Interceptors: Used to execute additional logic before and after request handling, commonly for logging, response data transformation, and exception handling.
  • Filters: Used to capture and handle unhandled exceptions, commonly for global error handling.
  • Middleware: Used to execute additional logic before the request reaches the route handler and before the response is sent to the client, commonly for logging, request validation, and response compression.

Conclusion

This article delved into guards, interceptors, filters, and middleware in NestJS, demonstrating their usage with examples in real-world scenarios. By using these tools appropriately, we can build powerful and flexible NestJS applications. We also discussed the differences between these tools to help you better understand their application scenarios.