Middleware is a fundamental aspect of any modern web application. In NestJS, a versatile, extensible framework for building efficient, scalable Node.js server-side applications, middleware plays a crucial role. Extending the capabilities of traditional middleware systems, NestJS empowers developers to create complex, feature-rich backend systems. This guide will elucidate the concepts, creation, and mastery of middleware in NestJS.
What is Middleware in NestJS?
Middleware, in a nutshell, are functions running before the route handlers in any web application. They operate on the request and response objects, performing tasks such as authentication, logging, or data parsing and have the power to decide whether to pass the request to the next middleware or to end the HTTP request-response cycle right there.
A NestJS middleware can be a function or a class decorated with @Injectable()
. When defined as a class, the class should implement the NestMiddleware
interface.
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class LogMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
console.log('Request...', req);
next();
}
}
In this example, LogMiddleware
logs each incoming request and then passes it on to the next middleware/ route handler via the next()
function.
Applying Middleware
Middleware is usually implemented in the configure()
method of a module that implements NestModule
interface.
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LogMiddleware } from './middlewares/log.middleware';
@Module({})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LogMiddleware)
.forRoutes('*');
}
}
In this code snippet, LogMiddleware
will apply to all routes. Contrarily, you can list specific routes, allowing you to customize exactly when your middleware is invoked.
Refining Middleware Application
Middleware can be applied globally, used specifically for certain HTTP methods and even be excluded from functioning on certain routes.
Applying Middleware Globally
To exert middleware on every incoming request regardless of the route, apply it globally as seen below:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { LogMiddleware } from './middlewares/log.middleware';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(LogMiddleware);
await app.listen(3000);
}
bootstrap();
Here, LogMiddleware
is applied to all incoming requests.
Specifying Middleware for Particular HTTP Methods
You can influence middleware to function on certain HTTP methods (GET, POST, PUT, DELETE etc.) using the RequestMethod
enum.
import { Module, NestModule, MiddlewareConsumer, RequestMethod } from '@nestjs/common';
import { LogMiddleware } from './middlewares/log.middleware';
@Module({})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LogMiddleware)
.forRoutes({ path: 'users', method: RequestMethod.GET });
}
}
In this case, LogMiddleware
applies only to GET requests made on the 'users' route.
Excluding Middleware from Certain Routes
Alternatively, you can indicate routes that will not use certain middleware using the exclude()
method.
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LogMiddleware } from './middlewares/log.middleware';
@Module({})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LogMiddleware)
.exclude('login', 'signup')
.forRoutes('*');
}
}
In this code, all routes except login
and signup
will apply LogMiddleware
.
Real-World Use Cases of Middleware
Middleware boasts a wide spectrum of applications, some of which are described below.
Logging
Middleware is ideal for creating logs about incoming requests. By logging details about the request, you can keep track of all network traffic, enabling you to spot patterns or potential problem areas.
Authentication
Middleware can control access to routes depending on the user's authentication status. You can design your middleware to decode and validate tokens attached to requests, adding the decoded user information to the request. This authenticated user then can be used directly by route handlers without having to validate the user there.
API Key Validation
Middleware can vet incoming requests for valid API keys. By checking the API key in the header of incoming requests, the middleware can filter out unauthorized requests even before they reach the route handler.
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class ApiKeyMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const apiKey = req.headers['x-api-key'];
const validApiKey = '123456'; //This should not be in plain text!
if (apiKey !== validApiKey) {
throw new Error('Invalid API Key');
}
next();
}
}
Conclusion
Middleware is a powerful, flexible feature in NestJS that allows developers to extend the functionality and control the behavior of their applications. By harnessing middleware to manage tasks such as logging, authentication, and API key validation, developers can create clean, maintainable, and efficient web applications with rich, robust backend systems.