nestjs学习15:如何动态读取不同环境的配置

首先我们需要区分下process.envprocess.argv

process.env 和 process.argv

这两个都是 Node.js 内置 process 对象 的核心属性,专门用来给程序传递外部参数 / 配置,是开发中最常用的环境变量和命令行参数工具。

process.argv 命令行参数

作用:获取运行 Node 程序时,在命令行里输入的参数。

js 复制代码
node app.js hello 123

console.log(process.argv);

输出结果(数组):

js 复制代码
[
  '/usr/local/bin/node',  // 第0项:Node 执行程序路径
  '/xxx/app.js',          // 第1项:当前执行的文件路径
  'hello',                // 第2项:你输入的第一个参数
  '123'                   // 第3项:你输入的第二个参数
]

我们一般从第 2 项开始取:

js 复制代码
const [node, file, ...args] = process.argv; 
console.log(args); // ['hello', '123']
  • 是什么:命令行输入的参数数组

  • 用来做:启动程序时传参数(如端口、文件名、模式)

  • 格式string[] 字符串数组

process.env 环境变量

作用:获取系统 / 当前运行环境的配置变量,不写在代码里的 "外部配置"。

终端临时设置环境变量并运行:

js 复制代码
# Mac/Linux
PORT=3000 node app.js

# Windows cmd
set PORT=3000 && node app.js

console.log(process.env.PORT); // 3000 
console.log(process.env.NODE_ENV); // undefined(没设置就是 undefined)

实际项目不会手动输命令,而是用 .env 文件:

安装依赖:

js 复制代码
npm install dotenv

新建 .env 文件

ini 复制代码
PORT=3000 
NODE_ENV=development 
API_URL=https://api.xxx.com

代码中使用:

js 复制代码
require('dotenv').config(); // 加载 .env 文件

console.log(process.env.PORT); // 3000
console.log(process.env.NODE_ENV); // development

那如果我有不同环境的配置文件呢?

可以通过可以通过 NODE_ENV 环境变量来切换:

js 复制代码
require('dotenv').config({
    path: process.env.NODE_ENVIRONMENT === 'production' ? '.env.production' : '.env',
})
  • 是什么 :存储环境配置的对象

  • 用来做 :区分开发 / 生产环境、存密钥、端口、接口地址等不适合写在代码里的配置

  • 格式{ [key: string]: string } 键值对对象

属性 含义 来源 格式 用途
process.argv 命令行参数 运行命令时手动输入 数组 传临时参数
process.env 环境变量 系统 /.env 文件 对象 存配置、密钥、环境区分

ymal 设置环境变量

除了使用.env来进行配置环境变量,如果环境变量复杂以后,推荐使用 yaml 格式的配置文件。

安装 js-yaml 包:

js 复制代码
npm install js-yaml

然后添加一个 hello.yaml 配置文件:

js 复制代码
application:
  host: 'localhost'
  port: 8080

db:
   mysql:
    url: 'localhost'
    port: 3306
    database: 'aaa'
    password: 'guang'

然后在 index.js 里用一下:

js 复制代码
const yaml = require('js-yaml');
const fs = require('fs');

const config = fs.readFileSync('./hello.yaml');

console.log(yaml.load(config));

可以看到,用对象的方式把 yaml 的配置给返回了。

yaml 的格式更适合有层次关系的配置,而 .env 更适合简单的配置。

同样,也可以通过 NODE_ENVIRMENT 环境变量来切换生产、开发的配置文件。

node 里的配置一般就用这两种方式。

nestjs中如何配置环境变量

其实上面的这两种配置方式(.env yaml),自己封装也不麻烦,封装个动态模块就好。

不过 Nest 提供了现成的封装:@nestjs/config。

安装 @nestjs/config 包,这个包同样是动态模块的方式,他有 forRoot 和 forFeature 两个方法。

在根目录加一个配置文件 .env:

js 复制代码
aaa=1
bbb=2

然后在 AppModule 里面引入:

然后在 AppController 里注入 ConfigService 来读取配置:

js 复制代码
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Inject(ConfigService)
  private configService: ConfigService;

  @Get()
  getHello() {
    return {
      aaa: this.configService.get('aaa'),
      bbb: this.configService.get('bbb')
    }
  }
}

一般都会把ConfigModule声明为全局模块,这样其他模块就不用imports了。

如果有多个配置文件,比如还有个 .aaa.env

js 复制代码
aaa=3

在 AppModule 里面这样指定:

js 复制代码
ConfigModule.forRoot({ envFilePath: [path.join(process.cwd(), '.aaa.env'), path.join(process.cwd(), '.env')] })

这里需要记住:不管 envFilePath 顺序怎么写,同名环境变量先出现的会保留,后出现的不会覆盖!

所以,可以看到 aaa 是 .aaa.env 里的,bbb 是 .env 里的。

这里,还有一个坑,上面使用了process.cwd,我们先区分下它和__dirname。

首先需要强调的是,虽然nestjs代码是 TypeScript,但 Nest.js 默认会将 TypeScript 编译为 CommonJS 格式的 JavaScript,编译后的代码在 dist 目录中,运行时使用的是 CommonJS 模块,所以,我们还是能在代码中使用__dirname的,在esm模块系统中不能使用。

__dirname:当前文件所在的目录,它永远指向当前这个 .ts/.js 文件所在的文件夹,不会变。

process.cwd: 永远指向你在终端里执行命令的根目录(项目根)。

目前,我们所有的.env文件都是放在项目的根目录下的,但是项目的根目录文件并不会打包到最后的dist文件夹下,最后会导致加载不到.env文件而报错。

所以需要把.env设置到src文件夹下,同时配置nest-cli.json中的assets 和 watchAssets:

js 复制代码
{
  "$schema": "https://json.schemastore.org/nest-cli",
  "collection": "@nestjs/schematics",
  "sourceRoot": "src",
  "compilerOptions": {
    "deleteOutDir": true,
    "watchAssets": true,
    "assets": ["**/.env*"]
  },
  "generateOptions": {
    "spec": false
  }
}

这样执行npm run build的时候,才会把.env文件打包到dist文件中。

当我们把.env文件放在src下之后,需要更改路由:path.join(process.cwd(), 'src/.aaa.env',这样在开发环境路径就是对的,但是在生成环境是没有src文件的,这样路径就不对了。

怎么办呢?

使用__dirname,它就是当前文件所在的路径,此时,不管开发环境还是生产环境,app.module.ts 和 .env 文件都在同一目录src下,所以要这样使用:

js 复制代码
ConfigModule.forRoot({
  // 声明为全局模块
  isGlobal: true,
  envFilePath: [path.join(__dirname, `.env`)],
})

为了区分不同环境下的配置,我们一般会创建不同环境的.env:

  • .env.development 写开发变量

  • .env.production 写生产变量

然后修改启动命令:

js 复制代码
"scripts": {
   "build": "cross-env NODE_ENV=production nest build",
  "start:dev": "cross-env NODE_ENV=development nest start --watch", 
  "start:prod": "cross-env NODE_ENV=production node dist/main", 
}

安装跨平台设置环境变量 cross-env

js 复制代码
npm install cross-env --save-dev

最后,配置如下:

js 复制代码
@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: [path.join(__dirname, `.env.${process.env.NODE_ENV}`)],
    }),
  ],
})

这样就完成了多环境的配置。

那如果我嫌 .env 里配置不够灵活,想在 ts 文件里配置呢?

我们写一个 config.ts:

js 复制代码
export default async () => {
    const dbPort = await 3306;

    return {
        port: parseInt(process.env.PORT, 10) || 3000,
        db: {
          host: 'localhost',
          port: dbPort
        }
    }
}

这里可以写异步逻辑,引入下:

js 复制代码
ConfigModule.forRoot({
   load: [config],
}),

在 Controller 里取出来使用即可。

这样,你可以动态加载配置。

后面将讲微服务的时候,会讲到配置中心,比如 nacos、etcd 这种中间件,到时候配置就是动态获取的。

而且这个配置文件里,你完全可以自己实现 yaml 文件的加载。

添加一个配置文件 aaa.yaml

js 复制代码
application:
  host: 'localhost'
  port: 8080

aaa:
   bbb:
    ccc: 'ccc'
    port: 3306

然后在 config2.ts 里加载下:

js 复制代码
import { readFile } from 'fs/promises';
import * as yaml from 'js-yaml';
import { join } from 'path';


export default async () => {
    const configFilePath = join(process.cwd(), 'aaa.yaml');

    const config = await readFile(configFilePath, {
        encoding: 'utf-8'
    });

    return yaml.load(config);
};

引入 :

js 复制代码
ConfigModule.forRoot({
   load: [config2, config],
}),

同样,前面覆盖后面的。

这样就正确读取了 yaml 配置。

此外,@nestjs/config 还提供了 forFeature 方法来返回动态模块,这里就不展开了。

相关推荐
牧码岛4 天前
服务端之NestJS请求解析体系、从HTTP报文到参数注入的工程化实践、控制器方法、装饰器、Headers、Query、Param、Body、Req
typescript·nestjs
小蜜蜂dry5 天前
nestjs学习 - 管道(pipe)
前端·nestjs
小蜜蜂dry5 天前
nestjs学习 - 拦截器(intercept)
前端·nestjs
小蜜蜂dry7 天前
nestjs学习 - 守卫
前端·nestjs
xiaoxue..9 天前
前后端双令牌认证(Access Token + Refresh Token)全方案实现:安全与体验兼得
前端·后端·web安全·面试·typescript·nestjs
helloweilei12 天前
NestJS系列(3)- Provider(提供者)
nestjs·全栈
helloweilei12 天前
NestJS系列(2)- 控制器(Controller)
nestjs·全栈
helloweilei12 天前
NestJS系列(1)- nestjs简介及项目初始化
nestjs·全栈
刘晓飞13 天前
nestjs的类为控制器(Controller)
nestjs