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
| Middleware | Interceptors |
|---|---|
| Hoạt động ở Express layer | Hoạt động ở NestJS layer |
| Có thể thay đổi request/response | Có thể thay đổi request/response |
| Không thể inject classes khác | Có thể inject dependencies |
Không có access vào execution context | Có 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.