Middleware in NestJS is a powerful concept that allows you to execute logic before or after handling incoming HTTP requests. Middleware can be used for tasks like logging, authentication, validation, and transformation of request data. In this module, we will explore how to use middleware in NestJS to preprocess requests effectively.
Table of Contents
- Introduction
- What is Middleware in NestJS?
- Creating Middleware in NestJS
- Applying Middleware Globally and Locally
- Common Use Cases for Middleware
- Accessing Request and Response Objects
- Chaining Middleware
- Best Practices for Middleware
- Conclusion
Introduction
Middleware acts as a gatekeeper in a NestJS application, sitting between the request and the controller handler. It provides an opportunity to execute logic, manipulate the request, or even modify the response before the request reaches the actual route handler.
In this module, we’ll dive into how to use middleware in NestJS for preprocessing requests. We’ll explore creating custom middleware, applying it globally or locally, and leveraging it to perform useful operations such as logging, authentication, and request validation.
What is Middleware in NestJS?
In NestJS, middleware is a function that is executed before the route handler. Middleware functions have access to the request (req
) and response (res
) objects and the next middleware function (next
). Middleware can modify the request object or perform any operation, and then pass control to the next middleware or route handler.
Middleware functions in NestJS can be applied globally (to every request) or locally (to specific routes or controllers).
Characteristics of Middleware:
- Middleware functions execute in the order they are defined.
- They can be asynchronous.
- Middleware can manipulate the request and response objects or terminate the request-response cycle by sending a response.
- Middleware functions can be applied globally or locally.
Creating Middleware in NestJS
To create middleware in NestJS, you need to implement the NestMiddleware
interface. The middleware function will receive the request, response, and the next
function, which should be called to pass control to the next handler.
Here’s an example of a simple logging middleware that logs every request to the console:
Step 1: Create a Logging Middleware
typescriptCopyEditimport { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class LoggingMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
console.log(`Request... ${req.method} ${req.originalUrl}`);
next(); // Pass control to the next handler
}
}
In this example:
- We inject the
LoggingMiddleware
using the@Injectable()
decorator. - The
use()
method is implemented to log the HTTP method and URL of each incoming request. - The
next()
function is called to pass control to the next middleware or route handler.
Step 2: Register the Middleware
To make this middleware functional, you must register it within a module. This is done in the module’s configuration file, typically app.module.ts
.
typescriptCopyEditimport { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggingMiddleware } from './logging.middleware';
@Module({
providers: [LoggingMiddleware],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(LoggingMiddleware).forRoutes('*');
}
}
In this example:
- The
LoggingMiddleware
is applied to all routes usingforRoutes('*')
. You can specify specific routes if needed (e.g.,forRoutes('users')
).
Applying Middleware Globally and Locally
Middleware can be applied globally to affect all routes in the application, or locally to specific routes or controllers.
Global Middleware
To apply middleware globally, use the app.use()
method in main.ts
or use the configure()
method in a module. Here’s an example in main.ts
:
typescriptCopyEditimport { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { LoggingMiddleware } from './logging.middleware';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(LoggingMiddleware); // Apply globally
await app.listen(3000);
}
bootstrap();
This ensures that the LoggingMiddleware
is executed for every incoming request.
Local Middleware
To apply middleware only to specific routes or controllers, use the MiddlewareConsumer
in the configure()
method of the respective module.
For example, to apply the LoggingMiddleware
only to the users
route:
typescriptCopyEditimport { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggingMiddleware } from './logging.middleware';
import { UserController } from './user.controller';
@Module({
controllers: [UserController],
})
export class UserModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(LoggingMiddleware).forRoutes('users'); // Apply only to '/users' route
}
}
Common Use Cases for Middleware
Middleware can be used to handle a variety of common tasks in NestJS applications. Here are some typical use cases:
1. Logging Requests
As shown earlier, middleware can be used to log details about incoming requests, such as HTTP method, URL, headers, and request body. This can help with debugging and tracking.
2. Authentication and Authorization
Middleware is often used for checking if the user is authenticated or authorized to access certain resources. For instance, you can create a middleware that checks for the presence of an authentication token in the request headers:
typescriptCopyEditimport { Injectable, NestMiddleware, UnauthorizedException } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class AuthMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const token = req.headers['authorization'];
if (!token) {
throw new UnauthorizedException('No authorization token found');
}
next();
}
}
3. Request Validation
Before passing the request to the controller, middleware can be used to validate or sanitize request data. This is often used in combination with libraries like class-validator
to perform data validation.
4. Rate Limiting
You can implement rate limiting in middleware to prevent abuse of your API by limiting the number of requests a user can make within a certain period of time.
5. Caching
Middleware can be used to implement caching strategies, storing responses in memory or a distributed cache (e.g., Redis) to speed up subsequent requests for the same data.
Accessing Request and Response Objects
Middleware functions have access to the request
and response
objects, allowing you to inspect or modify them. These objects are the same as those available in Express.js, and can be used for various tasks, such as reading headers, modifying the response body, or setting cookies.
typescriptCopyEdituse(req: Request, res: Response, next: NextFunction) {
// Accessing request headers
console.log(req.headers);
// Modifying the response body
res.setHeader('X-Custom-Header', 'Hello, world');
next();
}
Chaining Middleware
You can chain multiple middleware functions together to perform sequential tasks. For example, you might want to log a request, then authenticate it, and finally validate the request data. Here’s how you can apply multiple middleware in sequence:
typescriptCopyEditconsumer.apply(LoggingMiddleware, AuthMiddleware, ValidationMiddleware).forRoutes('*');
Middleware will be executed in the order it’s defined in the chain.
Best Practices for Middleware
- Keep Middleware Simple: Middleware should focus on one responsibility. Avoid adding too much logic to a single middleware function.
- Use Middleware for Cross-Cutting Concerns: Use middleware for tasks like logging, authentication, or rate limiting that affect many routes.
- Avoid Blocking: Make sure middleware functions call
next()
to allow the request to proceed. Never block the request-response cycle without sending a response. - Be Cautious with State: Avoid relying on state that persists across requests, unless using proper mechanisms like sessions or databases.
- Error Handling in Middleware: Handle any errors that occur within the middleware and pass them along the chain. Avoid letting errors crash the application.
Conclusion
Middleware in NestJS is an essential tool for preprocessing HTTP requests and centralizing tasks like logging, authentication, and validation. By using middleware effectively, you can manage repetitive tasks and ensure your application is both efficient and maintainable.
Now that you understand how to create and use middleware in NestJS, you can implement custom preprocessing logic for your application, improving performance, scalability, and security.