在 NestJS 中管理多个环境
问题
概括
所以,最近我开始在一家新的创业公司工作,每次我都会尝试采用一种新技术,无论是语言还是框架。(这并不总是推荐的,在这种情况下,我以前有使用 NestJS 的经验)
这次我选择采用 NestJS。之前我用过它做一些小项目,觉得它很有趣,所以我想为什么不把它用作我新创业公司的后端呢?感觉这真是个明智的选择。
问题
由于这并非我第一次参与创业公司项目,我实际上花了些时间妥善设置后端,而不是急于推出 MVP 版本。早期需要配置的一件事是,在不同模式下分离环境变量。
例如开发、测试、准备和生产
查看文档,没有关于如何做到这一点的真正建议,但它会给你提供一些线索,告诉你如何通过将各个部分组合在一起来实现这样的事情。
所以,我在这里记录一下我的做法,这样你就不用再浪费时间了。准备好了吗?开始吧。
步骤 1
在 NestJS 应用程序的根目录中创建以下结构。
步骤 2 - 初始化 ConfigModule
打开你的app.module
并写下以下内容
import { ConfigModule } from '@nestjs/config';
// ...skipping irrelevant code
@Module({
imports: [
ConfigModule.forRoot(),
PrismaModule,
ProductsModule,
AuthModule,
],
controllers: [AppController],
providers: [AppService],
})
// ...skipping irrelevant code
如果我们不传递任何options
参数,ConfigModule
默认情况下它会在根文件夹中查找 .env 文件,但它无法区分不同的环境。让我们继续下一步,让ConfigModule
查找位置和加载内容更加智能。
步骤 3 - 填充 development.env 文件
让我们填充development.env
文件作为创建单独环境的第一步。
JWT_SECRET=luckyD@#1asya92348
JWT_EXPIRES_IN=3600s
PORT=3000
步骤 4 - 填充configuration
文件
configuration.ts
- 其主要目的是创建一个对象(任何嵌套级别),以便您可以将值组合在一起并使其更容易使用。
另一个好处是,如果环境变量未定义,则可以提供默认值,并且最重要的是,您可以像下面的端口号一样对变量进行类型转换。
// configuration.ts
export const configuration = () => ({
NODE_ENV: process.env.NODE_ENV,
port: parseInt(process.env.PORT, 10) || 3001,
jwt: {
secret: process.env.JWT_SECRET,
expiresIn: process.env.JWT_EXPIRES_IN,
}
});
然后让我们将选项传递给ConfigModule
使用这个配置文件,如下所示:
import { configuration } from '../config/configuration'; // this is new
// ... skipping irrelevant code
@Module({
imports: [
ConfigModule.forRoot({
envFilePath: `${process.cwd()}/config/env/${process.env.NODE_ENV}.env`,
load: [configuration]
}),
PrismaModule,
ProductsModule,
AuthModule,
],
controllers: [AppController],
providers: [AppService],
})
// ...skipping irrelevant code
我们现在使用了两个选项来配置ConfigModule
。
- 加载
这应该是不言自明的,它加载我们提供的配置文件并执行上面提到的所有好操作。
- 环境文件路径
我们指向模块(在其下方使用 dotenv 包)根据process.env.NODE_ENV
环境变量读取 .env 文件。
process.cwd()
是一个提供当前工作目录路径的便捷命令
process.env.NODE_ENV
但是我们现在正在加载变量,您如何期望模块在加载环境变量之前使用该变量?!
好吧,请阅读下一步的更多信息!
步骤 5 - 初始化 NODE_ENV 环境变量
首先,NODE_ENV 变量是做什么用的?嗯,这是开发人员用来指示他们正在使用哪个环境的一种做法。
简而言之,NODE_ENV 通过查看其值让应用程序知道它是否应该在开发、生产或其他任何环境中运行。
实际上,加载环境变量的方法有很多,其中之一就是将变量内联到执行脚本中,如下所示:
// package.json
"scripts": {
"prebuild": "rimraf dist",
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "NODE_ENV=development nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "NODE_ENV=production node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json",
},
请注意上面的
NODE_ENV=development
和NODE_ENV=production
。
当我们使用一个示例执行脚本时,npm run start:dev
它实际上会设置变量,并且可以在 NestJS 应用中访问。太棒了,这回答了我们上面的问题。
Windows 用户必须安装
cross-env
软件包,因为 Windows 不支持这种加载变量的方式,并像这样更改命令"start:dev": "cross-env NODE_ENV=development nest start --watch"
步骤 6 - 使用
我们现在有两种方法来获取环境变量的值
方法 1
如上所示,我们可以使用 process.env 来访问变量。然而,在模块实例化期间访问环境变量时,这种方法存在一些缺陷,因此需要注意。
方法 2
使用ConfigService
访问变量。设置ConfigModule
now 使我们能够访问其服务,从而使我们能够访问变量
例子
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
import { ConfigService } from '@nestjs/config';
@Controller()
export class AppController {
constructor(private readonly appService: AppService, private configService: ConfigService) {}
@Get()
getHello(): string {
console.log(this.configService.get<string>('jwt.secret')
}
}
步骤 7 - 更新 .gitignore
如果你执行了 a,git status
你应该注意到该development.env
文件正在被监视并将被提交。不过,只要你不使用相同的值(例如在production.env
lets update .gitignore 中忽略.env
文件),这在某种程度上是可以接受的:
// .gitignore
// add at the bottom
**/*.env
!config/env/development.env
这里说的是忽略除.env
以下文件之外的所有文件development.env
(奖励)- 验证环境变量
现在我们已经完成了整个过程,但我们可以更进一步确保我们的变量具有正确的类型并且已加载。
步骤 1 - 安装joi
该库将通过将我们的环境变量与我们提供的进行比较来完成验证环境变量的繁重工作schema
。
npm install joi
OR
yarn add joi
第 2 步 - 填充 validation.ts
import * as Joi from 'joi';
export const validationSchema = Joi.object({
NODE_ENV: Joi.string().valid(
'development',
'production',
'test',
'provision',
),
JWT_SECRET: Joi.string().required(),
JWT_EXPIRES_IN: Joi.string().required(),
PORT: Joi.number().default(3000),
});
因此,我们上面所做的是确保 NODE_ENV 是提到的字符串之一,JWT_* 变量是strings
和required
,并且我们要求port
是一个数字并具有默认值(这就是为什么我们不要求required()
存在一个值)
请注意,验证模式命名必须与文件中的完全相同
.env
,而不是您在configuration.ts
步骤 3 - 更新选项ConfigModule
import { validationSchema } from '../config/validation';
@Module({
imports: [
ConfigModule.forRoot({
envFilePath: `${process.cwd()}/config/env/${process.env.NODE_ENV}.env`,
load: [configuration],
validationSchema,
}),
PrismaModule,
ProductsModule,
AuthModule,
],
controllers: [AppController],
providers: [AppService],
})
所以在这里我们导入并提供validationSchema
给模块。
练习:尝试将 NODE_ENV 设置为验证模式中定义的四个值以外的其他值,看看会发生什么
(奖励 2) - 避免到处导入配置模块
有一个便捷的选项可以避免在每个模块中导入配置模块,这非常简洁。它的名称isGlobal
如下,您可以在下面找到它的用法。
@Module({
imports: [
ConfigModule.forRoot({
envFilePath: `${process.cwd()}/config/env/${process.env.NODE_ENV}.env`,
isGlobal: true,
load: [configuration],
validationSchema,
}),
PrismaModule,
ProductsModule,
AuthModule,
],
controllers: [AppController],
providers: [AppService],
})
概括
您已经建立了一种灵活的方式,以不复杂的方式为每个环境设置环境变量,同时通过根据模式验证环境变量来维护类型和值的完整性。
我希望您发现这篇文章很有用,如果您想保持联系,您可以随时在Twitter上找到我。
鏂囩珷鏉ユ簮锛�https://dev.to/pitops/managing-multiple-environments-in-nestjs-71l