【前端学习】 NestJS 之 中间件 (Middleware)

中间件 (Middleware)

中间件是在路由处理程序之前调用的函数。中间件函数可以访问requestresponse对象,以及应用请求-响应周期中的next()中间件函数。next中间件函数通常由名为next的变量表示。

默认情况下,Nest 中间件是等同于express 中间件的。其中,中间件的功能是:

  • 执行任何代码;

  • 更改请求和响应对象;

  • 结束请求-响应循环;

  • 调用堆栈中的下一个中间件函数;

  • 如果当前中间件不在结束请求-响应循环中,必须调用next()来跳过控制到下一个中间件函数。否则,请求将被搁置。

对于我们使用者来说,可以在函数中 或 具有@Injectable()装饰器的类中实现自定义Nest 中间件。这个装饰器是不是很眼熟?没错,它在我们书写提供器 (provider) 时曾使用到过。

对于自定义Nest 中间件,类应该实现NestMiddleware接口,功能无特殊要求。

接下来让我们从使用类方法来实现一个简单的中间件函数。

首先,在src目录下创建一个文件夹名为common,随后再在common文件夹中创建一个子文件夹名为middleware,在middleware中创建我们的文件logger.middleware.ts

/* logger.middleware.ts */
import { Injectable, NestMiddleware } from "@nestjs/common";
import { NextFunction, Request, Response } from "express";

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
    use(req: Request, res: Response, next: NextFunction) {
        console.log('Requesting...');
        next();
    }
}

在这里可以提一嘴TypeScript 的好处:如下图所示。很明显,TS 是编译时报错,而JS 是运行时报错,这也是为什么TS 是JS 的超集,同时通过TS 可以使我们的代码编译性更好,更有健壮性的原因。

TypeScript 的机制

依赖注入 (dependency injection)

Nest 中间件同样支持依赖注入,就像提供器 (provider) 和控制器 (controller) 一样,它们能够注入同一模块中可用的依赖。同样也是通过constructor完成的。

*应用中间件

心细聪明的你应该不难发现,在我们app.module.ts文件中,@Module()装饰器里没有中间件的位置。但是不用担心,方法总比困难多,因为我们是通过 模块类的 configure()方法来设置中间件的。其中包含中间件的模块必须实现NestModule接口。接下来让我们在AppModule中来设置LoggerMiddleware

/* app.module.ts */
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { DogsModule } from './dogs/dogs.module';
import { LoggerMiddleware } from './common/middleware/logger.middleware';

@Module({
  imports: [DogsModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('dogs');
  }
}

上方代码示例中,我们为前几章在DogsController中定义的/cats路由处理程序设置了LoggerMiddleware。当然,我们也可以通过在配置中间件时将包含路由path和请求method的对象传递给forRoutes()方法。这样便可以将中间件限制为特定的请求方法。

下方代码示例中,我们基于上方代码,导入了RequestMethod枚举以引用我们所需的请求方法类型:

/* app.module.ts */
import { MiddlewareConsumer, Module, NestModule, RequestMethod } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { DogsModule } from './dogs/dogs.module';
import { LoggerMiddleware } from './common/middleware/logger.middleware';

@Module({
  imports: [DogsModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes({ path: 'dogs', method: RequestMethod.GET });
  }
}

一点启发:你可以使用 async/await来使configure()方法异步 (例如,在configure()方法主体内await 完成异步操作 ) 。

*中间件消费者 (consumer)

MiddlewareConsumer是一个辅助类。它提供了几种内置的方法来管理中间件 (middleware) 。forRoutes()方法可以接受单个字符串、多个字符串、一个RouteInfo对象、一个控制器类甚至 多个控制器类。在大多数情况下,可能只会传递以逗号分隔的控制器列表。

下方代码是单个控制器的示例:

/* app.module.ts */
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { DogsModule } from './dogs/dogs.module';
import { DogsController } from './dogs/dogs.controller';
import { LoggerMiddleware } from './common/middleware/logger.middleware';

@Module({
  imports: [DogsModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes(DogsController);
  }
}

提一嘴apply()方法可以采用单个中间件,或多个参数来指定多个中间件

排除路由 (excluding routes)

有时可能你会想排除某些路由应用中间件。这时可以使用exclude()方法来排除某些路由。此方法可以采用单个字符串、多个字符串 或 RouteInfo对象标识要排除的路由。如下:

// ...
consumer
  .apply(LoggerMiddleware)
  .exclude(
    { path: 'dogs', method: RequestMethod.GET },
    { path: 'dogs', method: RequestMethod.POST },
    'dogs/(.*)'
  )
  .forRoutes(DogsController);

在上方的示例中,LoggerMiddleware将绑定到DogsController内部定义的所有路由,除了传递给exclude()方法的三个路由之外。

exclude()方法会非常眼熟的,因为当你学习webpack 的时候,有些loader也会用到它。如果你还不了解webpack,那么请略过此行 : )

*函数式中间件 (functional middleware)

大家不难发现,我们写的LoggerMiddleware类其实是很简单的一个类,它没有成员也没有方法也没有依赖。那我们为啥不能用一个简单的函数来定义它呢?

包可以的 ~

这种类型的中间件就被称为函数式中间件。让我们将日志器中间件(LoggerMiddleware) 从基于类的中间件转换为函数式的中间件吧:

/* logger.middleware.ts */
import { NextFunction, Request, Response } from "express";

export function logger(req: Request, res: Response, next: NextFunction) {
    console.log(`Requesting...`);
    next();
};

然后在AppModule中使用它:

/* app.module.ts */
// ...
import { logger } from './common/middleware/logger.middleware';
// ...
consumer
  .apply(logger)
  .forRoutes(DogsController);

提一嘴:所以当中间件不需要任何依赖时,请考虑使用更简单的功能中间件来做替代方案。

你可能一直会有一个疑问:我代码里不是有console.log吗?怎么没在浏览器的控制台里看到?这是因为它打印的内容在我们终端里。如图:

打印的内容

多个中间件

上方也提及过,为了绑定顺序执行的多个中间件,只需在apply()方法中提供一个逗号分隔的列表就可以:

/* example */
consumer.apply(
	cors(),
	helmet(),
	logger
).forRoutes(DogsController);

全局中间件 (global middleware)

如果我们想一次将中间件绑定到每个已经注册的路由中,就可以使用INestApplication实例提供的use()方法:

全局全局,想全局,所以在main.ts文件中加入它:

/* main.ts */
// ...
const app = await NestFactory.create(AppModule);
app.use(logger);
await app.listen(3000);
// ...
### 关于 NestJS 的教程与学习 NestJS 是一种基于 TypeScript 的框架,它采用了现代 JavaScript 技术栈并结合了面向对象设计模式、函数式编程以及反应式编程的概念。由于其采用 MVC 架构,因此具备强大的 AOP(面向切面编程)能力[^1]。 以下是关于如何入门和学习 NestJS 的一些指导: #### 安装环境准备 在开始学习 NestJS 之前,请确保已安装以下工具: - **Node.js** (版本 10.x 或更高) - **npm 或 yarn**(用于包管理) - **代码编辑器**(推荐使用 Visual Studio Code) 这些工具的安装可以通过官方文档或其他教程完成[^3]。 #### 创建一个新的 NestJS 应用程序 通过命令行创建一个新项目非常简单。可以使用 `nestjs-cli` 工具来初始化项目结构。例如: ```bash npm i -g @nestjs/cli nest new project-name cd project-name npm run start ``` 这将生成一个基础的应用程序模板,并允许开发者立即运行服务以验证设置是否成功[^3]。 #### 核心概念介绍 NestJS 提供了许多核心特性支持复杂的企业级应用开发,其中包括但不限于以下几个方面: - **Middleware**: 中间件用于处理 HTTP 请求/响应周期中的逻辑操作[^4]。 - **Guards & Pipes**: Guards 控制访问权限;Pipes 则负责数据转换及校验工作[^1]。 - **Interceptors 和 Exception Filters**: Interceptors 可拦截调用链路执行额外业务逻辑;而异常过滤器则集中捕获错误信息返回给客户端[^1]。 - **DTO (Data Transfer Object)**: 数据传输对象定义 API 输入输出模型,有助于保持接口清晰一致[^1]。 #### 实践建议 为了更好地理解和运用所学知识,《JavaScript 前端开发案例教程:微课视频版》一书提供了大量实际应用场景下的练习机会,虽然主要针对前端领域,但对于熟悉整个 Web 开发流程同样有所帮助[^2]。 另外,在具体编码过程中可能会遇到循环依赖等问题,这时就需要借助 forwardRef 方法解决此类情况。下面是一个简单的例子展示两个相互依赖的服务是如何配置的: ```typescript import { Module, forwardRef } from '@nestjs/common'; import { ServiceA } from './service-a.service'; import { ServiceB } from './service-b.service'; @Module({ providers: [ { provide: 'SERVICE_A', useFactory: (b: ServiceB) => new ServiceA(b), inject: [forwardRef(() => 'SERVICE_B')], }, { provide: 'SERVICE_B', useFactory: (a: ServiceA) => new ServiceB(a), inject: [forwardRef(() => 'SERVICE_A')], }, ], }) export class AppModule {} ``` 此片段展示了如何利用 forward reference 解决循环注入问题[^4]。 最后需要注意的是,`.module.ts` 文件通常是每个功能模块的核心入口点之一,它们被标记为带有特定元数据的对象并通过装饰器形式声明出来。根模块尤其重要因为它包含了启动应用程序所需的关键组件和服务提供者列表[^5]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值