在 NestJS 中实现外部定义的配置设置

你可能已经花了一些时间使用了很棒的 NestJS 框架,现在是时候看看如何为你的配置设置获取一些外部定义的值,比如一些键或全局变量,例如:

  • 应用监听端口号
  • API 全局路由前缀
  • JWT 参数(令牌密钥、令牌过期时间,例如以秒为单位)
  • 数据库连接参数(类型、主机、端口、用户名、密码、数据库)
  • 等等。

NestJS 提供了使用外部配置文件和环境变量处理不同环境(开发、生产等)的必要文档。 因此,如果大家已经看过那里,那么下面提供的案例也将帮助大家开始实施你的(相对简单的)项目。

注意 :假设你已经为自己的项目安装了 @nestjs/config 包,如果还安装了 @nestjs/jwt 包并开始使用 JSON Web 令牌也很好。

因此,大家可以在下面找到在自己的实现中使用的 3 个最简单和最常见的案例。

基础

具有已定义属性及其值(如键/值对)的 .env.dev 环境配置文件

.env.dev

APP_PORT=3000
JWT_SECRET=abcdABCD1234554321
JWT_EXP=60

package.json 文件中的脚本

. . .
    "start:dev": "STAGE=dev nest start --watch",
    "start:debug": "STAGE=debug nest start --debug --watch",
    "start:prod": "STAGE=prod node dist/main",
. . .

例如,我们可以在终端中运行 dev 配置:

$ npm run start:dev

在 AppModule 中全局配置 ConfigModule

import { Module } from '@nestjs/common';
import { UsersModule } from './users/users.module';
import { AuthModule } from './auth/auth.module';
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [
    UsersModule,
    AuthModule,
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: [`.env.${process.env.STAGE}`],
    }),
  ],
})
export class AppModule {}
  • .forRoot() – 这个静态方法在 ConfigModule 中加载环境变量,配置 ConfigService 提供者,并使其在模块范围内,并且由于这是 AppModule,因此它可以在整个应用程序中使用。
  • isGlobal — 我们使用 isGlobal 属性并将其设置为 true,这样我们就可以在项目中普遍使用 ConfigModule,而不必在每个模块中单独导入它。
  • envFilePath——我们使用 envFilePath 属性从文件中加载环境变量——我们传递包含它们的文件的完整路径名。

现在,是时候在我们的项目中使用外部定义的值(通过 ConfigModule)了。

在类/repo/service 中使用 ConfigService

首先,我们必须将它添加到该类/属性/服务所属的 Module 的 imports 属性数组中。

imports: [AuthModule, ConfigModule],

在 UsersModule 中导入 ConfigModule

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { AuthModule } from 'src/auth/auth.module';
import { UsersRepo } from 'src/dataObjects/users.repo';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';

@Module({
  //imports: [AuthModule, ConfigModule],   
  imports: [AuthModule],
  controllers: [UsersController],
  providers: [UsersService, UsersRepo],
  exports: [UsersRepo],
})
export class UsersModule {}

请注意,在我们的例子中,我们已经在 AppModule 中设置了全局使用的 ConfigModule(即,我们将 isGlobal 属性设置为 true),因此,无需在导入属性数组中导入 ConfigModule。

然后,我们必须将它注入到该类/repo/service 的构造函数中:

. . .
  constructor(
    private jwtService: JwtService,
    private configService: ConfigService,        
  ) {
    this.myUsers = [this.user1, this.user2];
  }
. . .

最后,我们可以“正常”使用它:

. . .    
    const mySecret = this.configService.get('JWT_SECRET');    
    const verifyOptions = { secret: mySecret };
. . .

在 main.ts 中使用 ConfigService

由于 main.ts 仅包含 bootstrap() 函数,我们可以将 ConfigService 实例/上下文作为 const 获取,如下所示:

const configService = app.get(ConfigService);

之后,我们就可以使用实例的get方法了,正常的方式,e.g. 对于应用程序侦听端口:

const appPort = configService.get<number>('APP_PORT', 3000);
await app.listen(appPort);

如你所见(正如官方文档所述)“get() 方法还接受一个可选的第二个参数,定义一个默认值,当键不存在时将返回,如上所示”应用程序监听端口 , 默认值为 3000。

我们可以对环境文件中声明的任何其他键执行相同的操作。

main.ts 文件的示例:

import { Logger, ValidationPipe } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { NestFactory } from '@nestjs/core';
import { TransformInterceptor } from './app-interceptors/transform.interceptor';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const configService = app.get(ConfigService);

  const appGlobalPrefix = configService.get<string>('APP_GLOBAL_PREFIX', '');
  app.setGlobalPrefix(appGlobalPrefix);

  app.useGlobalPipes(new ValidationPipe());
  app.useGlobalInterceptors(new TransformInterceptor());
  //await app.listen(3000);
  const appPort = configService.get<number>('APP_PORT', 3000);
  await app.listen(appPort);

  const stage = process.env.STAGE;
  Logger.log(
    'App is running in "' +
      stage +
      '" stage, and it is listening at: http://localhost:' +
      appPort +
      '/' +
      appGlobalPrefix +
      '/',
  );
}
bootstrap();

在动态导入的模块中注入和使用 ConfigService

(它被导入到模块的导入属性数组中)

例如,当我们在另一个模块中导入 JwtModule 时就是这种情况,例如。 在授权模块中:AuthModule。 下面是我们如何导入 JwtModule 并直接注册其参数/属性的值:signOptions 中的 secret 和 expiresIn。

这里的第一个提示是异步注册 JwtModule,例如

JwtModule.registerAsync(. . .)

这是必要的,因为我们必须异步读取外部文件,例如 .env 文件。

然后,我们必须在注入属性数组中添加 ConfigModule。 之后,我们可以使用 useFactory() 动态地(和异步地)提供/注入 ConfigService 实例。

完整版 AuthModule 的示例:

import { forwardRef, Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { JwtModule } from '@nestjs/jwt';
import { UsersModule } from 'src/users/users.module';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';

@Module({
  imports: [
    forwardRef(() => UsersModule),
    // JwtModule.register({
    //   secret: 'myTopSecret54321', 
    //   signOptions: {
    //     expiresIn: 3600,
    //   },
    // }),
    JwtModule.registerAsync({
      // imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: async (configService: ConfigService) => ({
        secret: configService.get('JWT_SECRET'),
        signOptions: {
          expiresIn: configService.get<number>('JWT_EXP', 60),
        },
      }),
    }),
  ],
  controllers: [AuthController],
  providers: [AuthService],
  exports: [JwtModule],
})
export class AuthModule {}

同样在这里,也不需要在模块属性数组中声明 ConfigModule ,因为我们已经在 AppModule 中设置了 isGlobal: true 。