文章目录
中间件 (Middleware)
中间件是在路由处理程序之前调用的函数。中间件函数可以访问request
和response
对象,以及应用请求-响应周期中的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 可以使我们的代码编译性更好,更有健壮性的原因。
依赖注入 (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);
// ...