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.
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
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:
Bash1npm 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 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 thenext()
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.
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.
Here’s how to implement middleware that validates incoming requests using the class-validator
and class-transformer
packages:
TypeScript1@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
, orPATCH
before performing validation. This ensures that validation only occurs for methods that typically involve creating or updating resources. - We use
plainToInstance
fromclass-transformer
to convert the plain JSON request body into an instance of theCreateTodoDto
class. This conversion allows us to apply the validation rules defined in the DTO class. - The
validate()
function fromclass-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.
Here’s how to create middleware that logs the time it takes to process a request:
TypeScript1@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.
To apply these middleware functions in your NestJS application, configure them in the AppModule
:
TypeScript1export 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 theAppModule
class is used to set up middleware. - The
apply
method registers theValidationMiddleware
and specifies that it should be applied to all routes undertodos
. This ensures that any requests to these routes will go through the validation middleware before reaching the controller. - Similarly, the
apply
method registers theTimerMiddleware
globally for all routes ('*'
). This ensures that the timing of every request in the application is logged.
To complete the validation process, define validation rules in your DTOs using class-validator
:
TypeScript1export 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.
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!