Lesson 3
Middleware Configuration in NestJS
Middleware Configuration in NestJS

Welcome back to our course on enhancing your NestJS application with more robust features! In the previous lesson, we covered Database Migrations and how they help you maintain your database schema as your application evolves.

Now, let's dive into another essential component for building scalable applications: Middleware Configuration. Middleware in NestJS can be used for tasks like request validation, logging, and more.

What You'll Learn

In this lesson, you will gain hands-on experience in configuring and implementing middleware in a NestJS application, including:

  • Implementing validation middleware to validate incoming requests
  • Adding timer middleware to log request durations
  • Applying these middleware functions effectively within your NestJS application
Installing Required Packages

Before we begin, ensure you have the necessary packages installed to use class-validator and class-transformer in your NestJS application. This setup is already done in the Practice section:

Bash
1npm install class-validator class-transformer

These packages are used for validating data and transforming plain JavaScript objects into class instances, which is crucial for the validation process in NestJS.

Middleware in NestJS

Middleware is a function that is executed before the route handler and has access to the request and response objects, as well as the next() function. Middleware functions can perform various tasks, such as:

  • Executing any custom code.
  • Modifying the request or response objects.
  • Ending the request-response cycle.
  • Passing control to the next middleware function using next(). If the next() function is not called and the middleware does not end the cycle, the request will hang.

Middleware functions are useful for handling authentication, validation, logging, error handling, and more. They can operate globally, for specific routes, or even for certain HTTP methods.

Dependency Injection in Middleware

NestJS middleware fully supports Dependency Injection, just like controllers and providers. This means you can inject any services or dependencies directly into the middleware via the constructor, making middleware highly modular and reusable within the application.

Implementing Validation Middleware

Here’s how to implement middleware that validates incoming requests using the class-validator and class-transformer packages:

TypeScript
1@Injectable() 2export class ValidationMiddleware implements NestMiddleware { 3 async use(req: Request, res: Response, next: NextFunction) { 4 if (['POST', 'PUT', 'PATCH'].includes(req.method)) { 5 const dtoInstance = plainToInstance(CreateTodoDto, req.body); 6 const errors = await validate(dtoInstance); 7 if (errors.length > 0) { 8 throw new BadRequestException('Validation failed'); 9 } 10 } 11 next(); 12 } 13}

In this example:

  • We check if the HTTP request method is one of POST, PUT, or PATCH before performing validation. This ensures that validation only occurs for methods that typically involve creating or updating resources.
  • We use plainToInstance from class-transformer to convert the plain JSON request body into an instance of the CreateTodoDto class. This conversion allows us to apply the validation rules defined in the DTO class.
  • The validate() function from class-validator checks the transformed instance against the validation rules. The function returns an array of validation errors if any exist.
  • If validation errors are found (i.e., the errors array is not empty), a BadRequestException is thrown, which will stop the request from proceeding to the controller.
  • If no validation errors are found, the next() function is called to pass control to the next middleware or the route handler.
Implementing Timer Middleware

Here’s how to create middleware that logs the time it takes to process a request:

TypeScript
1@Injectable() 2export class TimerMiddleware implements NestMiddleware { 3 use(req: Request, res: Response, next: NextFunction) { 4 const start = Date.now(); 5 res.on('finish', () => { 6 const duration = Date.now() - start; 7 console.log(`${req.method} ${req.originalUrl} [${res.statusCode}] - ${duration}ms`); 8 }); 9 next(); 10 } 11}

This middleware:

  • Captures the start time when the request is received using Date.now().
  • Sets up an event listener on the response object to listen for the finish event, which indicates that the response has been sent.
  • When the response is finished, calculates the duration of the request by subtracting the start time from the current time using Date.now() again.
  • Logs the HTTP method, original URL requested, response status code, and the time taken to process the request to the console.
  • Calls the next() function to pass control to the next middleware or route handler after setting the event listener.
Applying Middleware in AppModule

To apply these middleware functions in your NestJS application, configure them in the AppModule:

TypeScript
1export class AppModule implements NestModule { 2 configure(consumer: MiddlewareConsumer) { 3 consumer 4 .apply(ValidationMiddleware) 5 .forRoutes('todos') 6 .apply(TimerMiddleware) 7 .forRoutes('*'); 8 } 9}

Here:

  • The configure method of the AppModule class is used to set up middleware.
  • The apply method registers the ValidationMiddleware and specifies that it should be applied to all routes under todos. This ensures that any requests to these routes will go through the validation middleware before reaching the controller.
  • Similarly, the apply method registers the TimerMiddleware globally for all routes ('*'). This ensures that the timing of every request in the application is logged.
Updating DTO for Validation

To complete the validation process, define validation rules in your DTOs using class-validator:

TypeScript
1export class CreateTodoDto { 2 @IsNotEmpty() 3 @IsString() 4 title: string; 5 6 @IsOptional() 7 @IsString() 8 description?: string; 9}

This DTO ensures that:

  • The title field is a non-empty string. The @IsNotEmpty() decorator enforces that the field should not be empty, and @IsString() ensures that the value is a string.
  • The description field is optional but must be a string if provided. The @IsOptional() decorator allows the field to be absent, and @IsString() ensures that it is a string if it is provided.
Why It's Important

Understanding and applying middleware is essential for:

  • Request Integrity: Ensures incoming requests contain valid data, preventing data inconsistencies.
  • Performance Monitoring: Timer middleware helps track request durations, identifying potential bottlenecks.
  • Security: Middleware can enforce security policies like input validation.
  • Scalability: Well-structured middleware supports application scalability and maintainability.

Now that you've learned how to apply essential middleware functions in your application, it's time to practice and make your NestJS app even more robust!

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