Nestjs中间件常见使用方式(class、函数中间件)

'# Nestjs中间件常见使用方式(class、函数中间件)

一、背景与问题

在构建复杂的Node.js应用时,中间件是实现请求处理流程的核心组件。Nestjs作为基于TypeScript的渐进式Node.js框架,提供了两种中间件实现方式:函数式中间件和基于类的中间件。这两种实现方式在底层原理上存在本质差异,但在实际开发中各有适用场景。

当前开发中常见的中间件使用问题包括:

  1. 中间件执行顺序理解错误导致逻辑混乱
  2. 未正确处理异常导致程序崩溃
  3. 未考虑性能影响造成请求延迟
  4. 安全性配置不当暴露敏感信息
  5. 依赖注入失效导致代码耦合

二、基本原理

1. 函数式中间件原理

函数式中间件是通过use方法注册的普通函数,其执行流程如下:

function logger(req: Request, res: Response, next: Function) {
  console.log(`Request: ${req.method} ${req.url}`);
  next();
}

底层实现通过fastify或express的中间件机制,将请求处理流程组织为链式调用。每个中间件函数接收三个参数:请求对象、响应对象和next函数。

2. 类中间件原理

类中间件通过@Injectable()装饰器注册,其执行流程如下:

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: Function) {
    console.log(`Request: ${req.method} ${req.url}`);
    next();
  }
}

底层通过@nestjs/common模块的中间件系统,将类方法注册为中间件实例。类中间件支持依赖注入和装饰器,可以更灵活地组织业务逻辑。

3. 中间件执行顺序

Nestjs中间件的执行顺序遵循以下规则:

  • 与路由绑定的中间件按声明顺序执行
  • 全局中间件在路由中间件之前执行
  • @UseFilters装饰器注册的异常处理中间件在最后执行

三、环境准备

创建Nestjs项目:

npm i -g @nestjs/cli
nest new nest-middleware-demo
cd nest-middleware-demo
npm install

项目结构:

src/
├── main.ts
├── app.controller.ts
├── app.module.ts
├── middleware/
│   ├── logger.middleware.ts
│   └── auth.middleware.ts
└── common/
    └── filters/
        └── http-exception.filter.ts

四、核心实现

1. 函数式中间件实现

// src/middleware/logger.middleware.ts
export function loggerMiddleware(req: Request, res: Response, next: Function) {
  console.log(`Request: ${req.method} ${req.url}`);
  next();
}

在路由中使用:

// src/app.controller.ts
import { Controller, Get, UseMiddleware } from '@nestjs/common';
import { loggerMiddleware } from '../middleware/logger.middleware';

@Controller()
export class AppController {
  @Get()
  @UseMiddleware(loggerMiddleware)
  getHello(): string {
    return 'Hello World';
  }
}

关键点说明:

  • 中间件函数必须接受三个参数
  • next()函数调用控制流程继续
  • 中间件可以修改请求/响应对象

2. 类中间件实现

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

@Injectable()
export class LoggerMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    console.log(`Request: ${req.method} ${req.url}`);
    next();
  }
}

在路由中使用:

// src/app.controller.ts
import { Controller, Get, UseMiddleware } from '@nestjs/common';
import { LoggerMiddleware } from '../middleware/logger.middleware';

@Controller()
export class AppController {
  @Get()
  @UseMiddleware(LoggerMiddleware)
  getHello(): string {
    return 'Hello World';
  }
}

关键点说明:

  • 使用@Injectable()进行依赖注入
  • 支持装饰器和类型校验
  • 更适合复杂业务逻辑处理

3. 异常处理中间件

// src/common/filters/http-exception.filter.ts
import { ExceptionFilter, Catch, HttpException } from '@nestjs/common';
import { Request, Response } from 'express';

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: any) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();
    const status = exception.getStatus();
    
    response.status(status).json({
      message: exception.message,
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
    });
  }
}

在模块中注册:

// src/app.module.ts
import { Module } from '@nestjs/common';
import { HttpExceptionFilter } from './common/filters/http-exception.filter';

@Module({
  imports: [],
  providers: [HttpExceptionFilter],
  controllers: [],
})
export class AppModule {}

关键点说明:

  • 通过@Catch装饰器捕获异常
  • 支持自定义异常处理逻辑
  • 适合统一错误处理

五、完整案例

构建一个用户认证系统:

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

@Injectable()
export class AuthMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    const token = req.headers['authorization'];
    
    if (!token || token !== 'secret-token') {
      throw new HttpException('Unauthorized', 401);
    }
    
    next();
  }
}
// src/app.controller.ts
import { Controller, Get, UseMiddleware } from '@nestjs/common';
import { AuthMiddleware } from './middleware/auth.middleware';

@Controller()
export class AppController {
  @Get()
  @UseMiddleware(AuthMiddleware)
  getHello(): string {
    return 'Hello World';
  }
}
// src/common/filters/http-exception.filter.ts
import { ExceptionFilter, Catch, HttpException } from '@nestjs/common';
import { Request, Response } from 'express';

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: any) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();
    const status = exception.getStatus();
    
    response.status(status).json({
      message: exception.message,
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
    });
  }
}
// src/app.module.ts
import { Module } from '@nestjs/common';
import { HttpExceptionFilter } from './common/filters/http-exception.filter';

@Module({
  imports: [],
  providers: [HttpExceptionFilter],
  controllers: [],
})
export class AppModule {}

运行测试:

npm run start

访问 http://localhost:3000 时会返回 401 Unauthorized 错误,而使用正确 token 时返回正常响应。

六、源码解析

以类中间件的执行流程为例,源码中关键部分如下:

// @nestjs/common/src/middleware/middleware.ts
export class NestMiddleware {
  // 中间件注册逻辑
  static registerMiddleware(
    app: FastifyInstance,
    middleware: NestMiddleware,
  ): void {
    const middlewares = app.middlewares;
    middlewares.push(middleware);
  }
  
  // 中间件执行逻辑
  static applyMiddlewares(
    req: Request,
    res: Response,
    next: Function,
    middlewares: NestMiddleware[],
  ): void {
    const executeMiddleware = (index: number) => {
      if (index >= middlewares.length) {
        return next();
      }
      const middleware = middlewares[index];
      if (middleware instanceof Function) {
        middleware(req, res, () => executeMiddleware(index + 1));
      } else {
        middleware.use(req, res, () => executeMiddleware(index + 1));
      }
    };
    executeMiddleware(0);
  }
}

关键点分析:

  • 中间件注册采用链式调用方式
  • 支持函数式和类中间件的统一处理
  • 通过递归方式执行中间件链

七、进阶使用

1. 中间件链式调用

@UseMiddlewares(LoggerMiddleware, AuthMiddleware)
getHello(): string {
  return 'Hello World';
}

2. 中间件装饰器组合

@UsePipes(new ValidationPipe())
@UseInterceptors(new LoggingInterceptor())

3. 中间件参数注入

@Injectable()
export class ConfigMiddleware {
  constructor(private readonly configService: ConfigService) {}
  
  use(req: Request, res: Response, next: Function) {
    console.log(this.configService.get('APP_NAME'));
    next();
  }
}

八、性能与工程实践

1. 性能优化策略

  • 中间件顺序优化:将耗时中间件放在最后
  • 避免不必要的中间件调用
  • 使用缓存中间件处理重复请求
  • 对关键中间件进行性能测试

2. 异常处理最佳实践

  • 为每个中间件单独处理异常
  • 使用@Catch装饰器统一处理
  • 避免在中间件中直接throw异常

3. 安全性考虑

  • 中间件不应暴露敏感信息
  • 对敏感中间件进行加密处理
  • 使用@UseFilters装饰器统一处理异常

4. 代码组织建议

  • 将中间件按功能分类组织
  • 使用@Injectable()进行依赖注入
  • 对复杂中间件进行单元测试

九、常见问题与踩坑

1. 中间件顺序错误

错误示例:

@UseMiddleware(AuthMiddleware, LoggerMiddleware)

问题:认证中间件应该在日志中间件之前执行

正确顺序:

@UseMiddleware(LoggerMiddleware, AuthMiddleware)

2. 未正确处理异常

错误示例:

throw new HttpException('...', 401);

问题:未使用@Catch装饰器处理异常

正确方式:

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  // ...
}

3. 中间件未注册

错误示例:

@UseMiddleware(LoggerMiddleware)

问题:未在模块中注册中间件

正确方式:

import { LoggerMiddleware } from './middleware/logger.middleware';

@Module({
  providers: [LoggerMiddleware],
  // ...
})

4. 未正确处理请求参数

错误示例:

req.headers['authorization'] // 未处理undefined情况

改进方式:

const token = req.headers['authorization'] || '';

十、最佳实践

  1. 类中间件推荐场景:

    • 需要依赖注入时
    • 逻辑复杂需要拆分时
    • 需要装饰器支持时
    • 需要类型校验时
  2. 函数中间件推荐场景:

    • 简单逻辑处理时
    • 不需要依赖注入时
    • 需要快速实现时
  3. 中间件使用规范:

    • 所有中间件必须使用@Injectable()装饰器
    • 异常处理必须使用@Catch装饰器
    • 中间件必须使用use方法
    • 中间件顺序必须符合业务逻辑
  4. 性能优化建议:

    • 对关键中间件进行缓存
    • 避免不必要的中间件调用
    • 使用性能分析工具进行优化
    • 对中间件进行单元测试

十一、总结

Nestjs中间件作为请求处理的核心组件,其合理使用对系统性能和可维护性至关重要。通过对比函数式中间件和类中间件的实现方式,我们可以发现:

  • 类中间件更适合复杂业务逻辑
  • 函数中间件适合简单处理逻辑
  • 中间件顺序直接影响执行流程
  • 异常处理是必须考虑的部分
  • 安全性需要特别注意

在实际开发中,建议遵循以下原则:

  1. 根据业务需求选择合适的中间件类型
  2. 保持中间件逻辑的单一职责
  3. 合理组织中间件的执行顺序
  4. 对关键中间件进行性能测试
  5. 使用统一的异常处理机制

通过合理使用中间件,可以显著提升系统的可维护性、可扩展性和安全性。在大型项目中,建议建立中间件管理规范,确保团队成员的代码质量和开发效率。

评论已关闭

推荐阅读

AIGC实战——Transformer模型
2024年12月01日
Socket TCP 和 UDP 编程基础(Python)
2024年11月30日
python , tcp , udp
如何使用 ChatGPT 进行学术润色?你需要这些指令
2024年12月01日
AI
最新 Python 调用 OpenAi 详细教程实现问答、图像合成、图像理解、语音合成、语音识别(详细教程)
2024年11月24日
ChatGPT 和 DALL·E 2 配合生成故事绘本
2024年12月01日
omegaconf,一个超强的 Python 库!
2024年11月24日
【视觉AIGC识别】误差特征、人脸伪造检测、其他类型假图检测
2024年12月01日
[超级详细]如何在深度学习训练模型过程中使用 GPU 加速
2024年11月29日
Python 物理引擎pymunk最完整教程
2024年11月27日
MediaPipe 人体姿态与手指关键点检测教程
2024年11月27日
深入了解 Taipy:Python 打造 Web 应用的全面教程
2024年11月26日
基于Transformer的时间序列预测模型
2024年11月25日
Python在金融大数据分析中的AI应用(股价分析、量化交易)实战
2024年11月25日
AIGC Gradio系列学习教程之Components
2024年12月01日
Python3 `asyncio` — 异步 I/O,事件循环和并发工具
2024年11月30日
llama-factory SFT系列教程:大模型在自定义数据集 LoRA 训练与部署
2024年12月01日
Python 多线程和多进程用法
2024年11月24日
Python socket详解,全网最全教程
2024年11月27日
python之plot()和subplot()画图
2024年11月26日
理解 DALL·E 2、Stable Diffusion 和 Midjourney 工作原理
2024年12月01日