什么是中间件?🤔
中间件(Middleware)是在路由处理程序之前调用的函数。中间件函数可以访问请求和响应对象,以及应用程序的请求-响应周期中的 next() 中间件函数。
Express vs NestJS 对比 🆚
Express 写法:
javascript
const express = require('express');
const app = express();
// 日志中间件
const logger = (req, res, next) => {
console.log(`${req.method} ${req.url}`);
next();
};
// 认证中间件
const auth = (req, res, next) => {
if (!req.headers.authorization) {
return res.status(401).json({ message: '未授权' });
}
next();
};
// 使用中间件
app.use(logger);
app.use('/protected', auth);NestJS 写法:
typescript
// logger.middleware.ts
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
console.log(`${req.method} ${req.url}`);
next();
}
}
// auth.middleware.ts
@Injectable()
export class AuthMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
if (!req.headers.authorization) {
throw new UnauthorizedException();
}
next();
}
}
// app.module.ts
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes('*')
.apply(AuthMiddleware)
.forRoutes('protected');
}
}中间件的类型 🎯
1. 类中间件
typescript
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
private readonly logger = new Logger('HTTP');
use(req: Request, res: Response, next: NextFunction) {
const { ip, method, originalUrl } = req;
const userAgent = req.get('user-agent') || '';
res.on('finish', () => {
const { statusCode } = res;
this.logger.log(
`${method} ${originalUrl} ${statusCode} - ${userAgent} ${ip}`
);
});
next();
}
}2. 函数中间件 🚀
typescript
// 简单的函数中间件
export function logger(req: Request, res: Response, next: NextFunction) {
console.log(`${req.method} ${req.url}`);
next();
}
// 在模块中使用
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(logger)
.forRoutes('*');
}
}中间件的应用方式 📝
1. 全局中间件
typescript
// main.ts
const app = await NestFactory.create(AppModule);
app.use(logger); // 👈 全局应用中间件
await app.listen(3000);2. 模块级中间件 🔧
typescript
@Module({
imports: [UsersModule],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware, AuthMiddleware) // 👈 可以应用多个中间件
.exclude( // 👈 排除特定路由
{ path: 'users', method: RequestMethod.GET },
'users/(.*)',
)
.forRoutes( // 👈 指定应用的路由
{ path: 'users', method: RequestMethod.POST },
{ path: 'auth/*', method: RequestMethod.ALL }
);
}
}异步中间件 ⚡
typescript
@Injectable()
export class AsyncMiddleware implements NestMiddleware {
constructor(private readonly configService: ConfigService) {}
async use(req: Request, res: Response, next: NextFunction) {
const apiKey = req.headers['x-api-key'];
const validApiKey = await this.configService.get('API_KEY');
if (apiKey !== validApiKey) {
throw new UnauthorizedException('Invalid API Key');
}
next();
}
}实战示例 💡
让我们创建一个完整的中间件系统:
typescript
// middlewares/request-logger.middleware.ts
@Injectable()
export class RequestLoggerMiddleware implements NestMiddleware {
private readonly logger = new Logger('HTTP');
use(req: Request, res: Response, next: NextFunction) {
const startTime = Date.now();
const { ip, method, originalUrl } = req;
const userAgent = req.get('user-agent') || '';
res.on('finish', () => {
const { statusCode } = res;
const responseTime = Date.now() - startTime;
this.logger.log(
`${method} ${originalUrl} ${statusCode} ${responseTime}ms - ${userAgent} ${ip}`
);
});
next();
}
}
// middlewares/rate-limiter.middleware.ts
@Injectable()
export class RateLimiterMiddleware implements NestMiddleware {
private readonly store = new Map<string, number[]>();
use(req: Request, res: Response, next: NextFunction) {
const ip = req.ip;
const now = Date.now();
const windowMs = 60000; // 1分钟
const max = 100; // 最大请求数
const requests = this.store.get(ip) || [];
const windowStart = now - windowMs;
// 清理旧的请求记录
const recentRequests = requests.filter(time => time > windowStart);
if (recentRequests.length >= max) {
throw new HttpException('Too Many Requests', 429);
}
recentRequests.push(now);
this.store.set(ip, recentRequests);
next();
}
}
// app.module.ts
@Module({
imports: [UsersModule, AuthModule],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(RequestLoggerMiddleware, RateLimiterMiddleware)
.exclude(
{ path: 'health', method: RequestMethod.GET }
)
.forRoutes('*');
}
}最佳实践 ✨
1. 保持中间件简单
typescript
// ❌ 不好的做法:中间件做太多事情
@Injectable()
export class ComplexMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
// 验证请求
// 修改请求体
// 查询数据库
// 发送邮件
// ...
next();
}
}
// ✅ 好的做法:每个中间件只负责一个功能
@Injectable()
export class AuthMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
// 只处理认证逻辑
next();
}
}2. 错误处理
typescript
@Injectable()
export class ErrorHandlerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
try {
next();
} catch (error) {
// 根据错误类型返回适当的响应
if (error instanceof UnauthorizedException) {
res.status(401).json({ message: '未授权' });
} else {
res.status(500).json({ message: '服务器错误' });
}
}
}
}3. 使用依赖注入
typescript
@Injectable()
export class CacheMiddleware implements NestMiddleware {
constructor(
private readonly cacheService: CacheService,
private readonly configService: ConfigService,
) {}
async use(req: Request, res: Response, next: NextFunction) {
const cacheKey = req.originalUrl;
const ttl = this.configService.get('CACHE_TTL');
const cachedData = await this.cacheService.get(cacheKey);
if (cachedData) {
return res.json(cachedData);
}
next();
}
}中间件执行顺序 🔄
中间件的执行顺序很重要:
typescript
consumer
.apply(Middleware1, Middleware2, Middleware3)
.forRoutes('*');
// 执行顺序:
// 1. Middleware1
// 2. Middleware2
// 3. Middleware3
// 4. Route Handler
// 5. Response总结 📝
通过这篇文章,我们学习了:
- 中间件的基本概念
- 不同类型的中间件
- 中间件的应用方式
- 异步中间件的实现
- 中间件的最佳实践
中间件是处理横切关注点(如日志、认证、限流等)的理想选择,合理使用中间件可以让我们的应用更加健壮和可维护。
下一篇文章,我们将探讨 NestJS 的管道(Pipe)系统。