Lesson 3
Configuring a Middleware and adding Validation in NestJS
Introduction to Middleware

Welcome to this lesson on configuring middleware and data validation in NestJS. So far, we have covered the basics of integrating MongoDB into your NestJS application and transforming data store objects into Data Transfer Objects (DTOs). These foundational steps have prepared you for building a functional ToDo app. Today, we'll take another important step by learning how to create and configure middlewares and add data validation in NestJS.

Middlewares play a crucial role in enterprise applications, providing functionalities like logging, authentication, and error handling. By the end of this lesson, you'll know how to create and integrate a timer middleware to monitor the performance of your application.

Validation, on the other hand, ensures that the data sent to your application meets predefined criteria. This is crucial for maintaining data integrity and security. By validating data, you can prevent common vulnerabilities, such as SQL injection and XSS attacks, and ensure that your application behaves as expected regardless of the input it receives.

Creating a Timer Middleware

A Middleware is a function that has access to the request and response objects and can modify them or end the request/response cycle. Let's create a timer middleware to log the duration of each request.

Step-by-Step Guide

app/src/middlewares/timer.middleware.ts

TypeScript
1import { Injectable, NestMiddleware } from '@nestjs/common'; 2import { Request, Response, NextFunction } from 'express'; 3 4@Injectable() 5export class TimerMiddleware implements NestMiddleware { 6 use(req: Request, res: Response, next: NextFunction) { 7 8 // Get the time we reveive the request 9 const start = Date.now(); 10 11 res.on('finish', () => { 12 // When the response finishes, determine how long it took to process the request and log it 13 const duration = Date.now() - start; 14 console.log(`${req.method} ${req.originalUrl} [${res.statusCode}] - ${duration}ms`); 15 }); 16 next(); 17 } 18}
Explanation of the Code
  • Imports: We import necessary classes and types from @nestjs/common and express.
  • @Injectable(): Marks the class as a provider that can be injected into other parts of the application.
  • NestMiddleware Interface: Ensures the class adheres to the middleware structure in NestJS.
  • use Method: Contains the logic to start a timer when the request is received and log the duration once the response is finished.
Understanding "use"

The use method above is the core of the middleware function. It intercepts requests made to the server, allowing us to process or log information before the request moves to the next middleware or the route handler. In this code, it contains the logic to start a timer when the request is received and log the duration once the response is finished.

  • req: This represents the incoming HTTP Request object. It contains information such as headers, the HTTP method (GET, POST, etc.), and the requested URL.
  • res: This represents the Response object. It is used to send a response back to the client after processing the request.
  • next: This is a callback function that tells NestJS to pass control to the next middleware or route handler. Without calling next(), the request would hang, as the server would not know that processing should continue.
Integrating Middleware in the NestJS Application

Next, we need to register and integrate the timer middleware into our application.

Step-by-Step Guide

app/src/app.module.ts

TypeScript
1import { Module, MiddlewareConsumer, NestModule } from '@nestjs/common'; 2import { MongooseModule } from '@nestjs/mongoose'; 3import { TodoModule } from './todo/todo.module'; 4import { TimerMiddleware } from './middlewares/timer.middleware'; 5 6@Module({ 7 imports: [ 8 MongooseModule.forRoot('mongodb://localhost/nestjs-todo'), 9 TodoModule, 10 ], 11}) 12export class AppModule implements NestModule { 13 configure(consumer: MiddlewareConsumer) { 14 consumer 15 .apply(TimerMiddleware) 16 .forRoutes('*'); 17 } 18 }
Explanation of the Code
  • Imports: We import required modules like MiddlewareConsumer and NestModule from @nestjs/common
  • apply Method: Registers the TimerMiddleware.
  • forRoutes('*'): Applies the middleware to all routes in the application.
Verification

Run the script and check the console logs to see the timer middleware outputs, e.g.,

1POST /todos [201] - 15ms 2POST /todos [400] - 10ms 3GET /todos [200] - 5ms
Adding Validation to CreateTodoDto

In addition to middleware, validation is a key aspect of building reliable and secure enterprise applications. Let's add validation to the CreateTodoDto to ensure that the data sent to your application meets certain criteria.

Step-by-Step Guide to Adding Validation
Install Class-Validator package
Bash
1npm install class-validator

These libraries provide decorators and tools to validate and transform objects, which integrate seamlessly with NestJS. We'll install this for you.

Add Validation Decorators to `CreateTodoDto`:

app/src/dtos/todo.dto.ts

TypeScript
1import { IsNotEmpty, IsString, IsOptional } from 'class-validator'; 2 3export class CreateTodoDto { 4 @IsNotEmpty() 5 @IsString() 6 title: string; 7 8 @IsOptional() 9 @IsString() 10 description?: string; 11}
Explanation of the Code
  • Imports: We import required decorators from class-validator.
  • @IsNotEmpty(): Ensures the field is not empty.
  • @IsString(): Ensures the field is a string.
  • @IsOptional(): Marks the field as optional.
Use the ValidationPipe

app/src/todo/todo.controller.ts

TypeScript
1@Post() 2async create(@Body(new ValidationPipe()) todo: CreateTodoDto): Promise<TodoDto> { 3 const newTodo = await this.todosService.createTodo(todo); 4 return transformTodoDto(newTodo); 5}

The ValidationPipe in this context is used to automatically validate the incoming request's data against a DTO (Data Transfer Object) before it reaches the controller's method.

Explanation of the Code
  • ValidationPipe: A built-in pipe provided by NestJS that can be used to validate the request data based on the rules defined in the DTO.
  • @Body(new ValidationPipe()): This decorator applies the ValidationPipe to the todo parameter, ensuring that the data conforms to the structure and validation rules specified in the CreateTodoDto class before it proceeds to the create method.
  • CreateTodoDto: This is the DTO that likely contains validation decorators like @IsString(), @IsNotEmpty(), etc., to enforce the required data format and constraints.

By using the ValidationPipe, the controller method create ensures that the todo object is valid, adhering strictly to the defined schema in CreateTodoDto. If the validation fails, an error response is sent back to the client automatically, and the method execution is halted. This helps in maintaining data integrity and preventing potential issues arising from malformed data.

Summary and Next Steps

In this lesson, we explored middleware and data validation in NestJS. We focused on creating and configuring a timer middleware as well as adding validation to our DTOs.

  • Recap: Middlewares and Validation are crucial for intercepting and handling requests/responses, and validation ensures data integrity and security. We created a timer middleware to log request durations and added validation to our CreateTodoDto to ensure data meets the required criteria.
  • Practice: Now that you have learned these concepts, you will get hands-on practice with exercises provided after this lesson. This will solidify your understanding of configuring middleware and adding validation in NestJS.
  • What's Next: In the upcoming lessons, we will delve deeper into other advanced topics such as advanced error handling, authentication, and authorization, which are essential for building robust enterprise applications.

Congratulations on making it this far. Let's move on to the exercises to reinforce your learning!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.