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

相关推荐
亮子AI7 天前
【NestJS】为什么return不返回客户端?
前端·javascript·git·nestjs
小p8 天前
nestjs学习2:利用typescript改写express服务
nestjs
Eric_见嘉13 天前
NestJS 🧑‍🍳 厨子必修课(九):API 文档 Swagger
前端·后端·nestjs
XiaoYu200222 天前
第3章 Nest.js拦截器
前端·ai编程·nestjs
XiaoYu200223 天前
第2章 Nest.js入门
前端·ai编程·nestjs
实习生小黄23 天前
NestJS 调试方案
后端·nestjs
当时只道寻常1 个月前
NestJS 如何配置环境变量
nestjs
濮水大叔1 个月前
VonaJS是如何做到文件级别精确HMR(热更新)的?
typescript·node.js·nestjs
ovensi1 个月前
告别笨重的 ELK,拥抱轻量级 PLG:NestJS 日志监控实战指南
nestjs