深入理解 NestJS 内核:中间件 Middleware

前言

在本章开始之前,我们需要先了解 NestJS 的 请求管线(Request Pipeline)机制。 其核心概念包括:

  • Middleware 中间件
  • Guard 守卫
  • Pipe 管道
  • Interceptor 拦截器
  • Exception Filter 异常过滤

初看可能陌生,但掌握后能让代码更优雅、易维护。

痛点:如果没有这些机制,会怎样?

假设你只用最原始的 Controller 调用:

ts 复制代码
@Controller('user')
export class UserController {
  @Post('create')
  create(@Body() dto: CreateUserDto) {
    if (!dto.name) throw new Error('missing name');
    if (!this.authService.verify(req.headers.token)) throw new Error('unauthorized');
    const start = Date.now();
    const result = this.userService.create(dto);
    console.log('耗时', Date.now() - start);
    return { data: result };
  }
}

这只是一个普通的业务逻辑,但你会发现:

  • 参数验证重复
  • 权限控制混杂业务逻辑
  • 异常处理零散
  • 日志和监控难统一
  • 小改动可能破坏流程

Nest 的 "分层执行管线"

Nest 把整个请求的处理过程,拆成 一条管线(Pipeline) ,每个环节处理一种类型的问题。

这就是 Middleware → Guard → Pipe → Interceptor → Controller → Exception Filter。

机制 出发点(要解决的问题) 执行阶段 典型关键词 失败后走向
Middleware 路由匹配前的全局逻辑:日志、CORS、body解析、静态文件、token提取等。Express 层面的问题。 Controller 之前(路由匹配前) 框架层面、通用逻辑 中断请求
Guard "这个请求是否被允许进入?"------访问控制与授权判断。 Controller 之前(匹配后执行) 权限控制、角色、token验证 Forbidden
Pipe "请求参数是否有效?类型是否正确?"------校验与转换。 Controller 参数解析阶段 DTO校验、类型转换 BadRequest
Interceptor "执行逻辑前后做些横切操作"------拦截执行、记录耗时、包裹响应。 Controller 调用前后 AOP、日志、响应包装 Exception Filter
Exception Filter "错误发生了,如何优雅返回?"------统一异常格式。 Controller 之后(异常时) 错误捕获、返回 结束请求

归纳一下:

  • Middleware:我在门外 ------ 拦截所有请求(Express层)
  • Guard:我在门口 ------ 判断是否能进当前 Controller
  • Pipe:我在门槛 ------ 校验和格式化参数
  • Interceptor:我在屋内 ------ 监听整个执行过程(包裹Controller)
  • Exception Filter:我在出口 ------ 接住一切错误

本章我们将从管线的第一环节 Middleware 开始,详细讲解它的使用与原理。

Middleware 深入讲解

在 NestJS 的请求管线中,Middleware 是第一道防线,在路由匹配之前执行,先于 Controller,可做日志记录、CORS 处理、请求体解析、Token 提取等

Middleware 类型与使用

NestJS 的 Middleware 主要有两种类型:

1. 函数式 Middleware

  • 最简单的形式,直接是一个函数。
  • 不支持依赖注入。
  • 使用场景:快速拦截、日志打印、简单处理请求。
ts 复制代码
import { Request, Response, NextFunction } from 'express';

function logger(req: Request, res: Response, next: NextFunction) {
  console.log(`[${req.method}] ${req.url}`);
  next(); // 调用 next() 将请求传给下一个 Middleware 或 Controller
}

// 全局注册
app.use(logger);

2. 类式 Middleware(推荐)

  • 实现 NestMiddleware 接口。
  • 支持依赖注入(可以通过 constructor 注入 Service)。
  • 使用场景:业务相关的预处理、模块化逻辑。
ts 复制代码
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { AuthService } from './auth.service';

@Injectable()
export class AuthMiddleware implements NestMiddleware {
  constructor(private authService: AuthService) {}

  use(req: Request, res: Response, next: NextFunction) {
    const token = req.headers['authorization'];
    if (!this.authService.verify(token)) {
      return res.status(401).json({ message: 'Unauthorized' });
    }
    next();
  }
}

注册方式

1. 全局 Middleware
ts 复制代码
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

function logger(req, res, next) {
  console.log(`[${req.method}] ${req.url}`);
  next();
}

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  // 全局注册函数式 middleware
  app.use(logger);
  await app.listen(3000);
}
bootstrap();
2. 模块级 Middleware
ts 复制代码
// user.module.ts
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { UserController } from './user.controller';
import { AuthMiddleware } from './auth.middleware';

@Module({
  controllers: [UserController],
})
export class UserModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(AuthMiddleware)       // 指定中间件
      .forRoutes(UserController);  // 模块级:拦截 UserController 内所有路由
  }
}
3. 路由级 Middleware
ts 复制代码
// user.module.ts
import { Module, NestModule, MiddlewareConsumer, RequestMethod } from '@nestjs/common';
import { UserController } from './user.controller';
import { LoggerMiddleware } from './logger.middleware';

@Module({
  controllers: [UserController],
})
export class UserModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes(
        { path: 'user/create', method: RequestMethod.POST }, // 仅拦截 POST /user/create
        { path: 'user/:id', method: RequestMethod.GET },     // 仅拦截 GET /user/:id
      );
  }
}
小结:
类型 注册方式 拦截范围 适用场景
全局 Middleware app.use(fn) 应用内所有路由 日志、CORS、全局鉴权
模块级 Middleware consumer.apply(...).forRoutes(Controller) 模块内所有路由 模块统一逻辑,支持 DI
路由级 Middleware consumer.apply(...).forRoutes({ path, method }) 指定接口 精确拦截特定路由或方法

Express middleware

为什么要讲 Express middleware?

在讲解 NestJS 的中间件之前,我们必须先理解 Express 的中间件模型

原因在于:

  • Nest 默认底层是基于 Express 框架构建 的(除非手动切换为 Fastify 适配器);

  • 因此,Nest 的中间件机制本质上就是 对 Express middleware 的封装与模块化抽象

  • 理解 Express middleware 的执行时机、参数形式和调用链机制,有助于我们更清楚 Nest 的内部工作原理。

换句话说,Nest 的 Middleware 是"更结构化的 Express Middleware"

如果你知道 app.use() 是如何工作的,你就能立刻明白 @MiddlewareConsumer().apply() 背后的逻辑。

Express middleware 的使用

在 Express 中,中间件(Middleware)是一个函数,它能访问 请求对象 (req)响应对象 (res)下一个中间件函数 (next)

定义中间件Logger 如下:

ts 复制代码
function logger(req, res, next) {
  console.log(`Request... ${req.method} ${req.url}`);
  next(); // 调用下一个中间件,否则请求会被挂起
}
  1. 用法一:全局中间件
ts 复制代码
const express = require('express');
const app = express(); 

app.use(logger); // 全局中间件
app.get('/users', (req, res) => res.send('User list'));

app.listen(3000);
  1. 用法二:匹配 /users* 的请求
ts 复制代码
const express = require('express');
const app = express(); 

app.use('/users', logger); // 只作用于具体某个路径
app.get('/users', (req, res) => res.send('User list'));

app.listen(3000);
  1. 用法三:匹配 router 模块
ts 复制代码
// admin.module.js
const express = require('express');
const adminRouter = express.Router();
const { adminAuthMiddleware } = require('./admin.middleware');

adminRouter.use(adminAuthMiddleware);
adminRouter.get('/dashboard', (req, res) => res.send('Admin Dashboard'));
adminRouter.get('/settings', (req, res) => res.send('Admin Settings'));
module.exports = { adminRouter }

// user.module.js
const userRouter = express.Router();
const { userLoggerMiddleware } = require('./user.middleware');

userRouter.use(userLoggerMiddleware);
userRouter.get('/profile', (req, res) => res.send('User Profile'));
userRouter.get('/settings', (req, res) => res.send('User Settings'));
module.exports = { userRouter }

// app.js
const express = require('express');
const app = express();
const { adminRouter } = require('./admin.module');
const { userRouter } = require('./user.module');

app.use('/admin', adminRouter); // admin 模块带 auth 中间件,且admin模块的路由前缀为/admin
app.use('/user', userRouter);   // user 模块带 logger 中间件,且user模块的路由前缀为/user

Express 中间件是整个框架的灵魂,它形成了一个"洋葱模型(Onion Model) "的请求管线,所有逻辑都在这个链上依次流转。

Nestjs middleware实现原理

其实 Nest 没有重新发明中间件,而是在 Express 机制上叠加了模块系统和依赖注入能力

这一章不再重复依赖注入的实现逻辑了,从下面可以看到源码中最终将中间件挂载在底层httpServer(express)上是通过 app.use(path, middlewareFunction) 来实现的。

ts 复制代码
  private async registerHandler(
    applicationRef: HttpServer,
    routeInfo: RouteInfo,
    proxy: <TRequest, TResponse>(
      req: TRequest,
      res: TResponse,
      next: () => void,
    ) => void,
  ) {
    const { method } = routeInfo;
    const paths = this.routeInfoPathExtractor.extractPathsFrom(routeInfo);
    const isMethodAll = isRequestMethodAll(method);
    const requestMethod = RequestMethod[method];
    // 这里的 createMiddlewareFactory 返回的是 app.use, 如果是在解析router的过程中则返回的是 controller 里的method.
    const router = await applicationRef.createMiddlewareFactory(method);
    const middlewareFunction = isMethodAll
      ? proxy
      : <TRequest, TResponse>(
          req: TRequest,
          res: TResponse,
          next: () => void,
        ) => {
          if (applicationRef.getRequestMethod?.(req) === requestMethod) {
            return proxy(req, res, next);
          }
          return next();
        };
    const pathsToApplyMiddleware = [] as string[];
    paths.some(path => path.match(/^\/?$/))
      ? pathsToApplyMiddleware.push('/')
      : pathsToApplyMiddleware.push(...paths);
    pathsToApplyMiddleware.forEach(path => router(path, middlewareFunction));
  }

使用 Nest Middleware 时有哪些注意点:

  1. 异步中间件支持度:

    Express 原生并不直接支持异步中间件,常用做法如下:

    ts 复制代码
    const asyncHandler = fn => (req, res, next) => {
      Promise.resolve(fn(req, res, next)).catch(next);
    };
    app.use(asyncHandler(async (req, res, next) => {
      await someAsyncTask();
      next();
    }));

    而 Nest 允许 middleware 写成 async use(req, res, next)。

  2. 中间件执行顺序:

    Express中,中间件的执行顺序就是注册顺序; 但在Nest中,它不直接暴露 app.use() 顺序,而是遵循以下三层规则👇:

    • 规则 1:在同一个模块中,中间件的顺序 = consumer.apply() 调用顺序

      ts 复制代码
      export class AppModule implements NestModule {
         configure(consumer: MiddlewareConsumer) {
           consumer
             .apply(LoggerMiddleware)          // ✅ 第 1 个执行
             .forRoutes('*');
      
           consumer
             .apply(AuthMiddleware)            // ✅ 第 2 个执行
             .forRoutes('admin');
         }
       }
    • 规则 2:跨模块时,顺序由模块的加载顺序决定

      假设你的模块结构如下,每个模块都实现了自己的中间件:

      ts 复制代码
      @Module({
         imports: [AuthModule, LoggerModule],
       })
      export class AppModule {}

      那么请求时执行顺序是: AuthMiddleware → LoggerMiddleware → Controller

    • 规则 3:全局中间件最先执行

      如果你通过 app.use() 注册中间件(即在 main.ts 中写):

      ts 复制代码
      const app = await NestFactory.create(AppModule);
      app.use(cors());
      app.use(helmet());

      那么这些会在所有模块中间件之前执行。

  3. 错误捕获:

    • Middleware 内抛出的异常如果不捕获,会交给 Nest 的 Exception Filter

总结:

NestJS 的 Middleware 是请求管线的第一道环节,用于框架层面的请求前处理(如日志、CORS、body解析、Token 提取等)。它基于底层 HTTP 框架(Express / Fastify),并对中间件进行了模块化封装,支持依赖注入和路由级控制,从而在大型项目中更灵活和可维护。

Middleware 的源码主要在 这里,之所以没有花很多篇幅去讲这个文件的实现,是觉得源码涉及细节较多,但对理解核心原理不是必须;如果后面有需要,会再细化这方面的知识点。

相关推荐
代码哈士奇2 天前
简单使用Nest+Nacos+Kafka实现微服务
后端·微服务·nacos·kafka·nestjs
濮水大叔4 天前
VonaJS AOP编程🚀大杀器🔪:外部切面
typescript·node.js·nestjs
濮水大叔4 天前
VonaJS AOP编程大杀器:外部切面
typescript·nodejs·nestjs
患得患失9496 天前
【NestJS】依赖注入:超越自动new的深层解析
nestjs
米诺zuo8 天前
nestjs中的SetMetadata用法
nestjs
濮水大叔8 天前
VonaJS AOP编程:魔术方法
typescript·nodejs·nestjs
葡萄城技术团队9 天前
2025 年 NestJS 仍值得后端开发者投入吗?深度解析其持久竞争力
nestjs
aricvvang9 天前
🚀 NestJS 使用 cache-manager-redis-store 缓存无效?真相在这里!
javascript·后端·nestjs