We use cookies to enhance your experience on the site
CodeWorlds

Middleware in NestJS

Ave again! Consul Caesar.js continues the administration lessons. Now we'll learn about middleware - the guards of our Roman forum who control every movement in the system.

Middleware in NestJS is like guards at the forum gates - everyone who wants to enter the system or move between sections must pass through their checkpoint. They can verify identity, register a visit, and even deny access if something's wrong.

What is Middleware?

Middleware are functions that execute between the request and the response. They have access to the request and response objects and to the

next()
function, which passes control to the next middleware in the queue.

1import { Injectable, NestMiddleware } from '@nestjs/common';
2import { Request, Response, NextFunction } from 'express';
3
4@Injectable()
5export class LegionLoggerMiddleware implements NestMiddleware {
6 use(req: Request, res: Response, next: NextFunction) {
7 const timestamp = new Date().toISOString();
8 const method = req.method;
9 const url = req.url;
10
11 console.log(`[FORUM] ${timestamp} - ${method} ${url} - Legionary in the system!`);
12
13 // Pass control to the next middleware/controller
14 next();
15 }
16}

Types of Middleware

1. Application-level middleware

Works like the main guard at the gate - checks everyone who enters the forum:

1// main.ts
2import { NestFactory } from '@nestjs/core';
3import { AppModule } from './app.module';
4
5async function bootstrap() {
6 const app = await NestFactory.create(AppModule);
7
8 // Global middleware
9 app.use((req, res, next) => {
10 console.log('⚔️ A new legionary has entered the system!');
11 next();
12 });
13
14 await app.listen(3000);
15}
16bootstrap();

2. Module-level middleware

Guards of specific forum sections:

1import { Module, MiddlewareConsumer, RequestMethod } from '@nestjs/common';
2
3@Module({
4 controllers: [TributesController],
5 providers: [TributesService],
6})
7export class TributesModule {
8 configure(consumer: MiddlewareConsumer) {
9 consumer
10 .apply(LegionLoggerMiddleware)
11 .forRoutes('tributes'); // Only for /tributes routes
12 }
13}

3. Route-specific middleware

Guards of specific chambers:

1@Module({})
2export class AppModule {
3 configure(consumer: MiddlewareConsumer) {
4 consumer
5 .apply(LegionLoggerMiddleware)
6 .exclude(
7 { path: 'tributes', method: RequestMethod.GET }, // Exclude GET /tributes
8 'legion/(.*)', // Exclude all legion routes
9 )
10 .forRoutes(TributesController); // Apply for the entire controller
11 }
12}

Middleware Authentication

One of the most important guards checks whether the legionary has the right to access resources:

1@Injectable()
2export class AuthenticationMiddleware implements NestMiddleware {
3 use(req: Request, res: Response, next: NextFunction) {
4 const legionToken = req.headers['legion-token'] as string;
5
6 if (!legionToken) {
7 return res.status(401).json({
8 message: 'Missing legionary token! Access only for legion members!',
9 code: 'NO_LEGION_TOKEN'
10 });
11 }
12
13 // Check if the token is valid
14 if (!this.isValidLegionToken(legionToken)) {
15 return res.status(403).json({
16 message: 'Invalid token! Are you sure you are one of us?',
17 code: 'INVALID_LEGION_TOKEN'
18 });
19 }
20
21 // Add legionary info to the request
22 req['legionary'] = this.getLegionaryFromToken(legionToken);
23 next();
24 }
25
26 private isValidLegionToken(token: string): boolean {
27 // Check the token in the legion database
28 const validTokens = ['ave-caesar', 'legio-x', 'tribune-marcus'];
29 return validTokens.includes(token);
30 }
31
32 private getLegionaryFromToken(token: string): any {
33 const legionaries = {
34 'ave-caesar': { name: 'Marcus Aurelius', rank: 'Centurion' },
35 'legio-x': { name: 'Julius Brutus', rank: 'Optio' },
36 'tribune-marcus': { name: 'Titus Flavius', rank: 'Tribunus' }
37 };
38 return legionaries[token];
39 }
40}

CORS Middleware

Controls which provinces (domains) can communicate with our forum:

1@Injectable()
2export class CorsMiddleware implements NestMiddleware {
3 use(req: Request, res: Response, next: NextFunction) {
4 const allowedOrigins = [
5 'https://imperium-frontend.com',
6 'https://provincia-map.com',
7 'http://localhost:3000'
8 ];
9
10 const origin = req.headers.origin;
11
12 if (allowedOrigins.includes(origin)) {
13 res.setHeader('Access-Control-Allow-Origin', origin);
14 }
15
16 res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS');
17 res.setHeader('Access-Control-Allow-Headers', 'Content-Type,legion-token');
18
19 // Handle preflight requests
20 if (req.method === 'OPTIONS') {
21 return res.status(200).end();
22 }
23
24 next();
25 }
26}

Request Logging Middleware

Logs all activities in the application:

1@Injectable()
2export class DetailedLoggerMiddleware implements NestMiddleware {
3 use(req: Request, res: Response, next: NextFunction) {
4 const startTime = Date.now();
5 const { method, url, headers, body } = req;
6 const userAgent = headers['user-agent'];
7 const legionToken = headers['legion-token'];
8
9 console.log(`⚔️ [${method}] ${url}`);
10 console.log(`🛡️ Legionary: ${legionToken || 'Unknown'}`);
11 console.log(`📋 User-Agent: ${userAgent}`);
12
13 if (method === 'POST' || method === 'PUT') {
14 console.log(`📦 Payload: ${JSON.stringify(body)}`);
15 }
16
17 // Response logging
18 const originalSend = res.send;
19 res.send = function(data) {
20 const endTime = Date.now();
21 const duration = endTime - startTime;
22
23 console.log(`✅ Status: ${res.statusCode}`);
24 console.log(`⏱ Time: ${duration}ms`);
25 console.log(`📜 Response: ${data}`);
26 console.log('---');
27
28 return originalSend.call(this, data);
29 };
30
31 next();
32 }
33}

Rate Limiting Middleware

Prevents a storm on the forum - limits the number of requests:

1@Injectable()
2export class RateLimitMiddleware implements NestMiddleware {
3 private requests = new Map<string, { count: number; resetTime: number }>();
4 private readonly MAX_REQUESTS = 100; // Maximum 100 requests
5 private readonly WINDOW_MS = 15 * 60 * 1000; // Per 15 minutes
6
7 use(req: Request, res: Response, next: NextFunction) {
8 const clientId = req.ip || 'unknown';
9 const now = Date.now();
10
11 // Check if the client has already made requests
12 let clientData = this.requests.get(clientId);
13
14 if (!clientData || now > clientData.resetTime) {
15 // New client or time window reset
16 clientData = {
17 count: 0,
18 resetTime: now + this.WINDOW_MS
19 };
20 }
21
22 clientData.count++;
23 this.requests.set(clientId, clientData);
24
25 // Check the limit
26 if (clientData.count > this.MAX_REQUESTS) {
27 return res.status(429).json({
28 message: 'Too many requests! The forum cannot handle such an assault!',
29 retryAfter: Math.ceil((clientData.resetTime - now) / 1000)
30 });
31 }
32
33 // Add headers with limit information
34 res.setHeader('X-RateLimit-Limit', this.MAX_REQUESTS);
35 res.setHeader('X-RateLimit-Remaining', this.MAX_REQUESTS - clientData.count);
36 res.setHeader('X-RateLimit-Reset', clientData.resetTime);
37
38 next();
39 }
40}

Error Handling Middleware

Handles exceptions and errors in the application:

1@Injectable()
2export class ErrorHandlingMiddleware implements NestMiddleware {
3 use(req: Request, res: Response, next: NextFunction) {
4 try {
5 next();
6 } catch (error) {
7 console.error('⚠️ System error!', error);
8
9 // Different error types
10 if (error.name === 'UnauthorizedError') {
11 return res.status(401).json({
12 message: 'You do not have permissions, young legionary!',
13 error: 'Unauthorized'
14 });
15 }
16
17 if (error.name === 'ValidationError') {
18 return res.status(400).json({
19 message: 'Invalid data! Check your reports!',
20 error: 'Bad Request',
21 details: error.details
22 });
23 }
24
25 // General server error
26 return res.status(500).json({
27 message: 'System failure! Engineers are already working on the fix!',
28 error: 'Internal Server Error'
29 });
30 }
31 }
32}

Conditional Middleware

Middleware that works only under certain conditions:

1@Injectable()
2export class ConditionalMiddleware implements NestMiddleware {
3 use(req: Request, res: Response, next: NextFunction) {
4 // Only during nighttime hours (extra protection)
5 const hour = new Date().getHours();
6 const isNightTime = hour < 6 || hour > 22;
7
8 if (isNightTime && req.url.includes('/tributes')) {
9 const nightGuardToken = req.headers['night-guard-token'];
10
11 if (!nightGuardToken) {
12 return res.status(403).json({
13 message: 'Tributes are guarded at night! You need a special permit!',
14 hint: 'Add night-guard-token in the header'
15 });
16 }
17 }
18
19 // Special middleware for attacks
20 if (req.headers['user-agent']?.includes('AttackBot')) {
21 return res.status(418).json({
22 message: 'Attack detected! Ballistae ready to fire! 🏹'
23 });
24 }
25
26 next();
27 }
28}

Registering Middleware in a Module

1@Module({
2 controllers: [TributesController, LegionController],
3 providers: [TributesService, LegionService],
4})
5export class ImperiumModule {
6 configure(consumer: MiddlewareConsumer) {
7 consumer
8 // Logger for all routes
9 .apply(DetailedLoggerMiddleware)
10 .forRoutes('*')
11
12 // Rate limiting for public APIs
13 .apply(RateLimitMiddleware)
14 .forRoutes(
15 { path: 'tributes', method: RequestMethod.GET },
16 { path: 'legion', method: RequestMethod.GET }
17 )
18
19 // Authentication for protected routes
20 .apply(AuthenticationMiddleware)
21 .exclude('auth/login', 'auth/register')
22 .forRoutes(
23 { path: 'tributes', method: RequestMethod.POST },
24 { path: 'tributes', method: RequestMethod.PUT },
25 { path: 'tributes', method: RequestMethod.DELETE },
26 TributesController
27 )
28
29 // Additional night protection
30 .apply(ConditionalMiddleware)
31 .forRoutes('tributes/*');
32 }
33}
Go to CodeWorlds