首先我们需要区分下process.env 和 process.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 方法来返回动态模块,这里就不展开了。