2、环境变量基础:Node.js 中的 process.env,.env 文件规范与 dotenv 库的使用
聊到多环境配置,第一个绕不开的概念就是环境变量。说白了,环境变量就是操作系统或运行环境里的一组键值对,用来告诉程序「你现在在哪儿、该用哪套配置」。
我记得刚入行那会儿,有个项目上线后 API 地址死活不对。查了半天,发现代码里写死了 localhost:3000。嗯,从那以后,我再也不敢把配置硬编码在代码里了。
Node.js 中的 process.env
在 Node.js 里,环境变量通过 process.env 访问。这是一个全局对象,包含了当前进程的所有环境变量。
// 最简单的用法
console.log(process.env.NODE_ENV);
// 输出可能是 'development' 或 'production'
// 设置默认值
const env = process.env.NODE_ENV || 'development';
console.log(`当前环境: ${env}`);
你可以在启动 Node.js 进程时传入环境变量:
# Linux / macOS
NODE_ENV=production node app.js
# Windows (CMD)
set NODE_ENV=production && node app.js
# Windows (PowerShell)
$env:NODE_ENV="production"; node app.js
我个人习惯用 cross-env 这个库,跨平台兼容,省心不少。
核心要点:process.env 是 Node.js 原生支持的,不需要额外安装任何包。但直接用它管理多环境配置,会有点「裸奔」的感觉——变量多了就乱,团队协作也容易出问题。
.env 文件规范
为了解决配置混乱的问题,社区逐渐形成了 .env 文件的约定。这个规范最早来自 Ruby 的 dotenv 项目,后来被前端社区广泛采纳。
一个典型的 .env 文件长这样:
# .env 文件示例
# 这是注释,以 # 开头
APP_NAME=MyApp
NODE_ENV=development
PORT=3000
API_BASE_URL=http://localhost:8080/api
DB_HOST=localhost
DB_PORT=5432
DB_USER=admin
DB_PASS=secret123
规范上有几个要点,我踩过坑,给你列一下:
- 键名全大写,单词间用下划线分隔。比如
API_BASE_URL,别写成apiBaseUrl。 - 值不加引号,除非值本身包含空格或特殊字符。加了引号,引号也会被当成值的一部分。
- 注释用 #,可以单独一行,也可以跟在值后面。
- 空行会被忽略,用来分组挺方便的。
- 不要提交 .env 文件到 Git,尤其是包含密码、密钥的。应该提交一个 .env.example 作为模板。
我曾经犯过的错:有次把 .env 文件提交到了 GitHub 公开仓库,里面包含了数据库密码。虽然几分钟后就删了,但已经被爬虫抓到了。嗯,那晚我改了一整夜的密码。
多环境场景下,通常会这样组织文件:
| 文件名 | 用途 | 是否提交 Git |
|---|---|---|
| .env | 默认配置,所有环境共享 | 否 |
| .env.development | 开发环境专用 | 是(示例值) |
| .env.production | 生产环境专用 | 是(示例值) |
| .env.local | 本地覆盖,优先级最高 | 否 |
| .env.example | 模板文件,给新同事参考 | 是 |
你想想看,如果每个环境都有一套独立的配置文件,部署时只需要加载对应的文件就行。是不是比在代码里写一堆 if-else 优雅多了?
dotenv 库的使用
Node.js 默认不会自动加载 .env 文件。你需要用 dotenv 这个库来「读取并注入」环境变量。
安装很简单:
npm install dotenv
基本用法:
// 在入口文件最顶部引入
require('dotenv').config();
// 现在 process.env 里就有 .env 文件的内容了
console.log(process.env.APP_NAME);
console.log(process.env.PORT);
dotenv 的 config() 方法默认读取项目根目录下的 .env 文件。你也可以指定路径:
// 加载特定环境的配置文件
require('dotenv').config({ path: '.env.production' });
// 或者加载多个文件,后面的会覆盖前面的
require('dotenv').config({ path: '.env.defaults' });
require('dotenv').config({ path: '.env.local' });
我的小技巧:在项目入口文件里,我会这样组织加载顺序:先加载 .env(公共配置),再加载 .env.local(本地覆盖)。这样既保证了基础配置,又允许开发者本地调试时覆盖某些值。
实际项目中,我通常会封装一个配置加载函数:
// config/index.js
const dotenv = require('dotenv');
const path = require('path');
// 确定当前环境
const env = process.env.NODE_ENV || 'development';
// 加载顺序:基础配置 → 环境配置 → 本地覆盖
const envFiles = [
'.env', // 所有环境共享
`.env.${env}`, // 当前环境专用
'.env.local', // 本地覆盖(不提交 Git)
];
envFiles.forEach(file => {
const filePath = path.resolve(process.cwd(), file);
const result = dotenv.config({ path: filePath });
if (result.error) {
// 文件不存在就跳过,不报错
if (result.error.code === 'ENOENT') {
console.warn(`⚠️ 配置文件 ${file} 不存在,跳过`);
} else {
console.error(`❌ 加载 ${file} 失败:`, result.error);
}
} else {
console.log(`✅ 已加载 ${file}`);
}
});
// 导出统一的配置对象
module.exports = {
appName: process.env.APP_NAME || 'MyApp',
port: parseInt(process.env.PORT, 10) || 3000,
apiBaseUrl: process.env.API_BASE_URL || 'http://localhost:8080',
db: {
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT, 10) || 5432,
user: process.env.DB_USER || 'root',
password: process.env.DB_PASS || '',
},
isDev: env === 'development',
isProd: env === 'production',
};
这样封装之后,项目里其他地方只需要 const config = require('./config') 就能拿到所有配置。而且类型转换、默认值都在一个地方处理,维护起来特别方便。
为什么推荐这种写法?因为 process.env 里所有值都是字符串。你想想看,PORT=3000 取出来是字符串 '3000',不是数字。不做类型转换,后面做端口判断时很容易出 bug。我就在这上面吃过亏——字符串比较 '3000' === 3000 返回 false,排查了半天。
实战中的注意事项
最后分享几个我在项目中积累的经验:
- 不要在代码里直接访问 process.env,而是通过一个统一的配置模块。这样哪天换配置管理方式,只需要改一个文件。
- .env 文件不要提交到 Git,但一定要提供 .env.example。新同事拉下代码,复制一份 .env.example 改成 .env 就能跑起来。
- 敏感信息(密码、密钥、Token)不要放在 .env 文件里提交到任何地方。生产环境的敏感信息应该通过 CI/CD 的变量注入或者密钥管理服务来提供。
- dotenv 只在开发环境用。生产环境通常由运维平台或容器编排工具直接注入环境变量,不需要 .env 文件。
好了,这一章的内容就这些。环境变量看似简单,但用好了能让你的项目配置管理井井有条。下一章我们聊聊如何在 Vite 和 Webpack 中使用环境变量,那又是另一番天地了。