组态

组态

用于在不同环境中运行的应用程序。根据环境,应使用各种配置变量集。例如,本地环境很可能在特定数据库凭证上进行中继,仅对本地数据库实例有效。为了解决这个问题,我们过去常常利用.env保存键值对的文件,其中每个键代表一个特定的值,因为这种方法非常方便。

但是当我们使用process全局对象时,由于测试类可能直接使用它,因此很难保持测试清洁。另一种方法是创建一个抽象层,ConfigModule它暴露出一个ConfigService带有加载的配置变量。

安装

某些平台会自动将我们的环境变量附加到process.env全局。但是,在当地环境中,我们必须手动处理它。为了解析我们的环境文件,我们将使用dotenv包。

$ npm i --save dotenv

服务

首先,让我们创建一个ConfigService类。

JS

import * as dotenv from 'dotenv'; import * as fs from 'fs'; export class ConfigService { private readonly envConfig: { [key: string]: string }; constructor(filePath: string) { this.envConfig = dotenv.parse(fs.readFileSync(filePath)) } get(key: string): string { return this.envConfig[key]; } }

该类采用单个参数a filePath,它是.env文件的路径。get()提供该方法以允许访问envConfig保存环境文件内定义的每个属性的私有对象。

最后一步是创建一个ConfigModule

JS

import { Module } from '@nestjs/common'; import { ConfigService } from './config.service'; @Module{ providers: [ { provide: ConfigService, useValue: new ConfigService(`${process.env.NODE_ENV}.env`), }, ], exports: [ConfigService], }) export class ConfigModule {}

ConfigModule寄存器中ConfigService,出口它。另外,我们传递了一个.env文件路径。根据实际执行环境,此路径将有所不同。现在,您可以简单地ConfigService在任何地方注入,并根据传递的密钥提取特定值。示例.env文件如下所示:

development.env

DATABASE_USER=test DATABASE_PASSWORD=test

使用ConfigService

要从我们访问环境变量ConfigService我们需要注入它。因此我们首先需要导入模块。

app.module.ts

@Module{ imports: [ConfigModule], ... })

之后,您可以使用注入令牌注入它。默认情况下,令牌等于类名(在我们的示例中ConfigService)。

app.service.ts

@Injectable() export class AppService { private isAuthEnabled: boolean; constructor(config: ConfigService) { // Please take note that this check is case sensitive! this.isAuthEnabled = config.get('IS_AUTH_ENABLED') === 'true' ? true : false; } }

ConfigModule您也可以将其声明ConfigModule为全局模块,而不是在所有模块中重复导入。

高级配置

我们刚刚实施了一个基础ConfigService。但是,这种方法有一些缺点,我们现在将解决:

  • 缺少环境变量的名称和类型(没有IntelliSense)

验证

我们将从提供的环境变量的验证开始。如果未提供所需的环境变量或者它们不符合您的预定义要求,则可能会抛出错误。为此,我们将使用npm包Joi。使用Joi,您可以定义一个对象模式并根据它来验证JavaScript对象。

安装Joi及其类型(适用于TypeScript用户):

$ npm install --save joi $ npm install --save-dev @types/joi

一旦安装了包,我们就可以转移到我们的ConfigService

config.service.ts

import * as Joi from 'joi'; import * as fs from 'fs'; export interface EnvConfig { [key: string]: string; } export class ConfigService { private readonly envConfig: EnvConfig; constructor(filePath: string) { const config = dotenv.parse(fs.readFileSync(filePath) this.envConfig = this.validateInput(config } /** * Ensures all needed variables are set, and returns the validated JavaScript object * including the applied default values. */ private validateInput(envConfig: EnvConfig): EnvConfig { const envVarsSchema: Joi.ObjectSchema = Joi.object{ NODE_ENV: Joi.string() .valid(['development', 'production', 'test', 'provision']) .default('development'), PORT: Joi.number().default(3000), API_AUTH_ENABLED: Joi.boolean().required(), } const { error, value: validatedEnvConfig } = Joi.validate( envConfig, envVarsSchema, if (error) { throw new Error(`Config validation error: ${error.message}` } return validatedEnvConfig; } }

由于我们的设置默认值NODE_ENVPORT如果我们不提供环境文件这些变量验证也不会失败。不过,我们需要明确提供API_AUTH_ENABLED。如果我们的.env文件中的变量不是模式的一部分,验证也会抛出错误。此外,Joi尝试将env字符串转换为正确的类型。

类属性

对于每个配置属性,我们必须添加一个getter函数。

config.service.ts

get isApiAuthEnabled(): boolean { return Boolean(this.envConfig.API_AUTH_ENABLED }

用法示例

现在我们可以直接访问类属性。

app.service.ts

@Injectable() export class AppService { constructor(config: ConfigService) { if (config.isApiAuthEnabled) { // Authorization is enabled } } }