Node.js 中使用env文件

Node.js 中 dotenv、pm2 与 process.env 的正确使用姿势

------为什么 pm2 能读到 env,而 node xxx.js 读不到?

这是一个我在真实项目中踩到的坑:
同一份代码,用 pm2 启动一切正常,但直接 node xxx.js 就报 MySQL Access denied for user ''

最终定位到:dotenv 并没有在当前 Node 进程中执行


一、问题现象

报错信息

text 复制代码
Access denied for user ''@'localhost' (using password: NO)

从报错可以看出两个关键点:

  • MySQL 用户名是空字符串:user ''
  • 没有使用密码:using password: NO

.env 明明已经写了:

env 复制代码
DB_USER=root
DB_PASSWORD=xxxx

而且:

  • 用 pm2 启动服务一切正常
  • node testApi/deleteUser.js 就直接报错

二、核心结论(一句话版)

.env 只是一个文件,不是环境变量
dotenv 只会在"当前 Node 进程"里生效一次

所以:

  • pm2 启动的进程 👉 一开始就有 env
  • 手动 node xxx.js 👉 没有 dotenv,就没有 env

三、dotenv 到底做了什么?

js 复制代码
require('dotenv').config()

这行代码的作用只有一个:

.env 文件的内容注入到 process.env

它不会:

  • 自动全局生效
  • 自动影响其他 Node 进程
  • 自动影响你之后手动 node xxx.js 启动的脚本

四、为什么 pm2 能读到 env,而 node 不行?

1️⃣ pm2 的常见行为

pm2 通常通过以下方式之一提供环境变量:

ecosystem.config.js
js 复制代码
env: {
  DB_USER: 'root',
  DB_PASSWORD: 'xxx'
}
shell 环境变量
bash 复制代码
export DB_USER=root
pm2 start index.js
pm2 启动目录中的 .env

👉 pm2 启动 Node 进程时,process.env 已经被填充


2️⃣ 手动 node 启动的情况

bash 复制代码
node testApi/deleteUser.js

如果这个文件里:

  • ❌ 没有 require('dotenv').config()
  • ❌ shell 里也没有 export env

那么:

js 复制代码
process.env.DB_USER === undefined
process.env.DB_PASSWORD === undefined

最终 MySQL 连接配置就会变成:

js 复制代码
user: ''
password: undefined

五、dotenv 写在哪才是"正确的"?

✅ 正确原则(非常重要)

dotenv 只应该写在「进程入口文件」里

什么是"入口文件"?

任何一个你可能直接运行的文件:

bash 复制代码
node index.js
node testApi/deleteUser.js
node scripts/migrate.js

这些 都是入口


六、正确示例

✅ 示例 1:最简单、安全(推荐)

js 复制代码
// testApi/deleteUser.js
require('dotenv').config()

const { deleteById } = require('../repos/userRepo')
deleteById(1)
js 复制代码
// repos/userRepo.js
// 不要写 dotenv
console.log(process.env.DB_USER) // 可以正常读取

✅ 示例 2:统一 bootstrap(工程级推荐)

js 复制代码
// bootstrap.js
require('dotenv').config()
js 复制代码
// index.js
require('./bootstrap')
require('./app')
js 复制代码
// testApi/deleteUser.js
require('../bootstrap')
require('../repos/userRepo')

👉 所有入口统一初始化环境


七、为什么不要在 export 文件里写 dotenv?

❌ 错误示例:

js 复制代码
// repos/userRepo.js
require('dotenv').config() // ❌ 不推荐

原因:

  • 模块职责不清晰
  • 被 require 时机不可控
  • 单测 / 脚本 / CLI 混乱
  • 容易造成"有时能读到、有时读不到"

八、一个非常实用的防坑技巧(强烈建议)

在创建数据库连接前,直接硬失败

js 复制代码
const must = (key) => {
  const val = process.env[key]
  if (!val) throw new Error(`${key} is missing`)
  return val
}

mysql.createPool({
  host: must('DB_HOST'),
  user: must('DB_USER'),
  password: must('DB_PASSWORD'),
  database: must('DB_NAME'),
})

👉 以后不会再看到:

text 复制代码
user ''
using password: NO

九、最终总结(工程级结论)

✅ dotenv 只影响当前 Node 进程

✅ pm2 ≠ node

✅ dotenv 只属于「入口文件」

❌ export 的模块里不要写 dotenv

记住一句话就够了:

"dotenv 是进程初始化的一部分,不是业务逻辑的一部分。"

相关推荐
摘星编程3 小时前
React Native for OpenHarmony 实战:Picker 选择器组件详解
javascript·react native·react.js
摘星编程4 小时前
React Native for OpenHarmony 实战:VirtualizedList 虚拟化列表
javascript·react native·react.js
摘星编程4 小时前
React Native for OpenHarmony 实战:RefreshControl 下拉刷新组件
javascript·react native·react.js
鸣弦artha6 小时前
Flutter框架跨平台鸿蒙开发——Extension扩展方法
android·javascript·flutter
筱歌儿8 小时前
TinyMCE-----word表格图片进阶版
开发语言·javascript·word
Ama_tor8 小时前
obsidian进阶の插件系列|Templater从小白到菜鸟
javascript·markdown·插件·obsidian
wuhen_n9 小时前
初识TypeScript
javascript·typescript
w***76559 小时前
JS vs jQuery:核心差异解析
开发语言·javascript·jquery
踢球的打工仔9 小时前
typescript-类
前端·javascript·typescript
大阳光男孩10 小时前
ElementUI表格懒加载子级更新数据刷新不生效问题
前端·javascript·elementui