Skip to main content

Middleware trong NestJS

Middleware là một function hoặc class được thực thi trước khi request tới controllers. Nó có quyền truy cập vào request object, response object, và next function trong request-response cycle. Middleware có thể thực hiện các tác vụ như authentication, logging, error handling, v.v.

Khái Niệm Middleware

Middleware trong NestJS được kế thừa từ Express.js. Nó là một function có signature:

(request, response, next) => void

Middleware có thể:

  • Thực hiện code tùy ý
  • Modify request/response objects
  • Kết thúc request-response cycle
  • Gọi next() function để chuyển control tới middleware tiếp theo

Request Processing Pipeline

Request

Global Middleware (1)

Global Middleware (2)

Module Middleware (1)

Module Middleware (2)

Guards (xác thực)

Interceptors (before)

Pipes (validation)

Controller

Interceptors (after)

Response

Functional Middleware

Cách đơn giản nhất để tạo middleware:

import { Request, Response, NextFunction } from 'express';

export function loggingMiddleware(
req: Request,
res: Response,
next: NextFunction,
) {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.path}`);
next();
}

Sử dụng Functional Middleware

import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';
import { AppController } from './app.controller';
import { loggingMiddleware } from './middleware/logging.middleware';

@Module({
controllers: [AppController],
})
export class AppModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(loggingMiddleware)
.forRoutes('*'); // Áp dụng cho tất cả routes
}
}

Class-Based Middleware

Để có tính tổ chức tốt hơn, sử dụng class-based middleware:

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class LoggingMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const startTime = Date.now();

// Log thông tin request
console.log(`[${new Date().toISOString()}] ${req.method} ${req.path}`);

// Hook vào end event của response
res.on('end', () => {
const duration = Date.now() - startTime;
console.log(`Response: ${res.statusCode} - ${duration}ms`);
});

next();
}
}

Sử dụng Class-Based Middleware

import { Module, MiddlewareConsumer, NestModule } from '@nestjs/common';
import { AppController } from './app.controller';
import { LoggingMiddleware } from './middleware/logging.middleware';

@Module({
controllers: [AppController],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggingMiddleware)
.forRoutes('*'); // Áp dụng cho tất cả routes
}
}

Selective Route Middleware

Áp dụng cho Routes Cụ Thể

@Module({
controllers: [UsersController],
})
export class UsersModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(AuthMiddleware)
.forRoutes(
{ path: 'users', method: RequestMethod.POST },
{ path: 'users/:id', method: RequestMethod.DELETE },
{ path: 'users/:id', method: RequestMethod.PUT },
);
}
}

Exclude Routes

import { RequestMethod } from '@nestjs/common';

export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggingMiddleware)
.exclude(
{ path: 'health', method: RequestMethod.GET },
{ path: 'status', method: RequestMethod.GET },
)
.forRoutes('*');
}
}

Sử dụng Controller Classes

@Module({
controllers: [UsersController, ProductsController],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
// Middleware áp dụng cho tất cả routes trong UsersController
consumer
.apply(AuthMiddleware)
.forRoutes(UsersController);

// Middleware áp dụng cho tất cả routes trong ProductsController
consumer
.apply(LoggingMiddleware)
.forRoutes(ProductsController);
}
}

Chaining Middleware

@Module({})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(CorsMiddleware, LoggingMiddleware, AuthMiddleware)
.forRoutes(UsersController)
.apply(CompressionMiddleware)
.forRoutes(FilesController);
}
}

Ví Dụ Thực Tế

1. Authentication Middleware

import { 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 authHeader = req.headers.authorization;

if (!authHeader) {
throw new UnauthorizedException('Missing authorization header');
}

const [scheme, token] = authHeader.split(' ');

if (scheme !== 'Bearer' || !token) {
throw new UnauthorizedException('Invalid authorization header');
}

// Validate token (simplified)
if (token === 'invalid-token') {
throw new UnauthorizedException('Invalid token');
}

// Attach user info to request
(req as any).user = { id: 1, email: 'user@example.com' };

next();
}
}

2. Request Logging Middleware

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class RequestLoggingMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const startTime = Date.now();
const { method, originalUrl, ip } = req;

console.log(`\n=== Incoming Request ===`);
console.log(`Method: ${method}`);
console.log(`URL: ${originalUrl}`);
console.log(`IP: ${ip}`);
console.log(`Time: ${new Date().toISOString()}`);

// Log request body (for POST/PUT/PATCH)
if (['POST', 'PUT', 'PATCH'].includes(method)) {
console.log(`Body:`, req.body);
}

// Log response
res.on('end', () => {
const duration = Date.now() - startTime;
console.log(`\n=== Outgoing Response ===`);
console.log(`Status: ${res.statusCode}`);
console.log(`Duration: ${duration}ms`);
console.log(`================================\n`);
});

next();
}
}

3. CORS Middleware

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class CorsMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const allowedOrigins = [
'http://localhost:3000',
'http://localhost:3001',
'https://example.com',
];

const origin = req.headers.origin as string;

if (allowedOrigins.includes(origin)) {
res.header('Access-Control-Allow-Origin', origin);
res.header('Access-Control-Allow-Credentials', 'true');
res.header('Access-Control-Allow-Methods', 'GET,HEAD,PUT,PATCH,POST,DELETE');
res.header('Access-Control-Allow-Headers', 'Content-Type,Authorization');
}

if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}

next();
}
}

4. Request Timeout Middleware

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class TimeoutMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const timeout = setTimeout(() => {
if (!res.headersSent) {
res.status(408).json({ message: 'Request timeout' });
}
}, 30000); // 30 seconds

res.on('end', () => {
clearTimeout(timeout);
});

next();
}
}

5. Request Body Parser Middleware

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class BodyParserMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
let data = '';

req.on('data', (chunk) => {
data += chunk;
});

req.on('end', () => {
try {
if (data) {
(req as any).rawBody = data;
req.body = JSON.parse(data);
}
} catch (error) {
return res.status(400).json({ message: 'Invalid JSON' });
}
next();
});
}
}

6. User Context Middleware

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { UserService } from '../users/users.service';

@Injectable()
export class UserContextMiddleware implements NestMiddleware {
constructor(private userService: UserService) {}

async use(req: Request, res: Response, next: NextFunction) {
const userId = (req as any).user?.id;

if (userId) {
const user = await this.userService.findOne(userId);
(req as any).user = user;
}

next();
}
}

7. Rate Limiting Middleware

import { Injectable, NestMiddleware, TooManyRequestsException } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class RateLimitMiddleware implements NestMiddleware {
private requests: Map<string, { count: number; resetTime: number }> = new Map();
private readonly maxRequests = 100;
private readonly timeWindow = 60000; // 1 minute

use(req: Request, res: Response, next: NextFunction) {
const ip = req.ip;
const now = Date.now();
const record = this.requests.get(ip);

if (!record || now > record.resetTime) {
this.requests.set(ip, { count: 1, resetTime: now + this.timeWindow });
return next();
}

if (record.count >= this.maxRequests) {
throw new TooManyRequestsException('Too many requests');
}

record.count++;
next();
}
}

8. Compression Middleware

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import compression from 'compression';

@Injectable()
export class CompressionMiddleware implements NestMiddleware {
private compressionHandler = compression();

use(req: Request, res: Response, next: NextFunction) {
this.compressionHandler(req, res, next);
}
}

Global Middleware

Middleware toàn cục (không cần khai báo trong module)

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { LoggingMiddleware } from './middleware/logging.middleware';

async function bootstrap() {
const app = await NestFactory.create(AppModule);

// Áp dụng middleware toàn cục
app.use(LoggingMiddleware);

await app.listen(3000);
}
bootstrap();

Middleware vs Interceptors

MiddlewareInterceptors
Hoạt động ở Express layerHoạt động ở NestJS layer
Có thể thay đổi request/responseCó thể thay đổi request/response
Không thể inject classes khácCó thể inject dependencies
Không có access vào execution contextCó access vào execution context
Dùng cho global concerns (logging, CORS)Dùng cho application-specific logic

MiddlewareConsumer API

export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
// apply() - Áp dụng middleware(s)
.apply(
MiddlewareA,
MiddlewareB,
MiddlewareC,
)
// forRoutes() - Chỉ định routes
.forRoutes(
{ path: 'users', method: RequestMethod.GET },
{ path: 'users/:id', method: RequestMethod.POST },
UserController,
)
// Có thể chain nhiều middleware configurations
.apply(MiddlewareD)
.exclude(
{ path: 'health', method: RequestMethod.GET },
)
.forRoutes(AdminController);
}
}

Best Practices

1. Middleware Ordering

Thứ tự middleware quan trọng:

export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(CorsMiddleware) // CORS đầu tiên
.forRoutes('*')
.apply(LoggingMiddleware) // Logging thứ hai
.forRoutes('*')
.apply(AuthMiddleware) // Authentication thứ ba
.forRoutes(AdminController);
}
}

2. Avoid Heavy Operations

Middleware nên nhẹ nhàng, tránh operations nặng:

// ❌ Sai - Database query trong middleware
@Injectable()
export class BadMiddleware implements NestMiddleware {
constructor(private db: DatabaseService) {}

use(req: Request, res: Response, next: NextFunction) {
// Lấy toàn bộ users từ database
const users = this.db.getAllUsers(); // HEAVY!
next();
}
}

// ✅ Đúng - Lightweight operations
@Injectable()
export class GoodMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const startTime = Date.now();
res.on('end', () => {
console.log(`Duration: ${Date.now() - startTime}ms`);
});
next();
}
}

3. Error Handling

Middleware nên handle errors gracefully:

@Injectable()
export class SafeMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
try {
// Middleware logic
next();
} catch (error) {
console.error('Middleware error:', error);
res.status(500).json({ message: 'Internal server error' });
}
}
}

4. Specificity

Áp dụng middleware cho routes cụ thể, không toàn cục nếu không cần:

// ❌ Sai - AuthMiddleware toàn cục
consumer.apply(AuthMiddleware).forRoutes('*');

// ✅ Đúng - Chỉ cho protected routes
consumer
.apply(AuthMiddleware)
.forRoutes(
AdminController,
ProfileController,
);

5. Type Safety

Extend Request object một cách an toàn:

// Tạo custom Request type
export interface CustomRequest extends Request {
user?: {
id: string;
email: string;
role: string;
};
}

@Injectable()
export class TypeSafeAuthMiddleware implements NestMiddleware {
use(req: CustomRequest, res: Response, next: NextFunction) {
req.user = {
id: '1',
email: 'user@example.com',
role: 'admin',
};
next();
}
}

Complete Example

// middleware/auth.middleware.ts
import { Injectable, NestMiddleware, UnauthorizedException } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { JwtService } from '@nestjs/jwt';

@Injectable()
export class AuthMiddleware implements NestMiddleware {
constructor(private jwtService: JwtService) {}

use(req: Request, res: Response, next: NextFunction) {
const authHeader = req.headers.authorization;

if (!authHeader) {
return next();
}

const [scheme, token] = authHeader.split(' ');

if (scheme !== 'Bearer') {
throw new UnauthorizedException('Invalid auth scheme');
}

try {
const payload = this.jwtService.verify(token);
(req as any).user = payload;
} catch (error) {
throw new UnauthorizedException('Invalid token');
}

next();
}
}

// middleware/logging.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class LoggingMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const { method, originalUrl } = req;
const startTime = Date.now();

res.on('end', () => {
const duration = Date.now() - startTime;
console.log(
`${method} ${originalUrl} ${res.statusCode} - ${duration}ms`,
);
});

next();
}
}

// app.module.ts
import { Module, MiddlewareConsumer, NestModule } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { AppController } from './app.controller';
import { AuthMiddleware } from './middleware/auth.middleware';
import { LoggingMiddleware } from './middleware/logging.middleware';

@Module({
imports: [JwtModule.register({ secret: 'secret' })],
controllers: [AppController],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggingMiddleware)
.forRoutes('*')
.apply(AuthMiddleware)
.exclude(
{ path: 'auth/login', method: 'POST' },
{ path: 'auth/register', method: 'POST' },
)
.forRoutes('*');
}
}

Kết Luận

Middleware là công cụ mạnh mẽ để xử lý cross-cutting concerns trong NestJS như:

  • Logging
  • Authentication
  • CORS
  • Rate limiting
  • Request transformation
  • Error handling

Sử dụng middleware đúng cách giúp bạn:

  • Tách biệt concerns
  • Tái sử dụng code
  • Đơn giản hóa controllers
  • Xây dựng ứng dụng modular và maintainable

Middleware được kế thừa từ Express, nên bạn có thể sử dụng hầu hết các middleware Express có sẵn trong NestJS applications.