config、dotenv、dotenv-safe 和 env-cmd 都是用于管理应用程序配置和环境变量的 JavaScript 工具,但它们的定位和能力有显著差异。dotenv 是最基础的 .env 文件加载器;dotenv-safe 在其基础上增加了必需变量校验;config 提供了完整的分层配置系统,支持多环境和嵌套结构;而 env-cmd 是一个命令行工具,用于在执行命令前从文件注入环境变量,适用于任何语言环境。
在现代前端和 Node.js 应用开发中,安全、灵活地管理环境变量是架构设计的基础环节。config、dotenv、dotenv-safe 和 env-cmd 是四种广泛采用的方案,但它们的设计哲学、使用场景和能力边界截然不同。本文将从工程实践角度,深入剖析这些工具的核心机制、适用边界与真实权衡。
dotenv:最轻量的 .env 加载器dotenv 的唯一职责是读取项目根目录下的 .env 文件,并将其内容注入到 process.env 中。它不提供校验、分层配置或命令行集成,纯粹做“加载”这件事。
// .env
DB_HOST=localhost
API_KEY=secret123
// index.js
require('dotenv').config();
console.log(process.env.DB_HOST); // 'localhost'
注意:
dotenv不会覆盖已存在的环境变量。如果DB_HOST已在系统中设置,.env中的值会被忽略。
dotenv-safe:带强制校验的 dotenv 增强版dotenv-safe 在 dotenv 基础上增加了必需变量校验。它要求你提供一个 .env.example(或自定义名称)文件,列出所有必须存在的变量名。启动时若缺失任一必需变量,程序会直接报错退出。
// .env.example
DB_HOST=
API_KEY=
// .env (缺少 API_KEY)
DB_HOST=localhost
// index.js
require('dotenv-safe').config();
// 抛出错误: Missing environment variables: API_KEY
这种“fail-fast”机制非常适合 CI/CD 或生产环境,避免因配置缺失导致运行时诡异错误。
config:面向复杂应用的分层配置系统config 不局限于环境变量,它构建了一套完整的分层配置体系。配置来源包括:
config/default.json)config/production.json)config/local.json)// config/default.json
{
"db": {
"host": "localhost",
"port": 5432
}
}
// config/production.json
{
"db": {
"host": "prod-db.example.com"
}
}
// index.js
const config = require('config');
console.log(config.get('db.host')); // 生产环境输出 'prod-db.example.com'
环境变量可通过命名规则覆盖配置,例如 export db__port=6432 会覆盖 db.port。
env-cmd:通过命令行注入环境变量env-cmd 本身不是代码库,而是一个 CLI 工具。它允许你在运行命令前,从指定文件加载环境变量,适用于任何语言编写的脚本。
// .env.production
DB_HOST=prod-db.example.com
LOG_LEVEL=error
# package.json
{
"scripts": {
"start:prod": "env-cmd -f .env.production node server.js"
}
}
在 server.js 中,你可以直接访问 process.env.DB_HOST,无需引入任何依赖。
dotenv:静默忽略缺失变量,可能导致 undefined 引发运行时错误。dotenv-safe:启动时严格校验,缺失必填变量立即失败。config:调用 config.get('missing.key') 时抛出异常,但不会在启动时全局校验。env-cmd:无内置校验,行为同原生 process.env。// dotenv-safe 示例:强制校验
require('dotenv-safe').config({
allowEmptyValues: true, // 允许空值(默认 false)
example: '.env.required' // 自定义模板文件
});
config 需特别注意:其配置文件(如 local.json)可能意外提交到 Git。官方建议通过 .gitignore 排除,并使用 custom-environment-variables.json 将敏感字段映射到环境变量。dotenv 系列:.env 文件通常加入 .gitignore,但需团队规范约束。env-cmd:配置文件同样需 .gitignore 保护,但因其不嵌入代码,风险略低。dotenv / dotenv-safe / env-cmd:仅支持扁平键值对(如 DB_HOST),无法直接表达嵌套对象。config:原生支持 JSON/YAML 的嵌套结构,适合复杂配置。// config 支持嵌套
const port = config.get('server.port');
const timeout = config.get('server.timeout.connect');
若需在 dotenv 中模拟嵌套,需自行解析:
// .env
SERVER_PORT=3000
SERVER_TIMEOUT_CONNECT=5000
// 手动构建对象
const serverConfig = {
port: process.env.SERVER_PORT,
timeout: {
connect: process.env.SERVER_TIMEOUT_CONNECT
}
};
config:通过文件命名约定(default.json, development.json, production.json)自动加载对应环境配置。dotenv 系列:需手动指定文件路径:// 根据 NODE_ENV 加载不同文件
require('dotenv').config({
path: `.env.${process.env.NODE_ENV}`
});
env-cmd:通过 -f 参数显式指定文件:env-cmd -f .env.staging npm run start
dotenvdotenv 支持,只需创建 .env 文件即可。无需额外依赖或复杂配置。# .env
VITE_API_BASE=/api
VITE_APP_NAME=MyApp
注意:Vite 要求变量以
VITE_开头才能暴露给客户端代码。
dotenv-safedotenv-safe 的启动校验能提前暴露问题。// server.js
require('dotenv-safe').config({
example: '.env.example',
error: '⚠️ 缺少必要环境变量,请检查 .env 文件'
});
configconfig 的分层模型能显著提升可维护性。// config/custom-environment-variables.json
{
"db": {
"password": "DB_PASSWORD" // 从环境变量 DB_PASSWORD 读取
}
}
env-cmdenv-cmd 是通用解决方案。# 运行 Python 脚本并注入变量
env-cmd -f .env.local python migrate.py
在实际项目中,这些工具常被组合使用:
env-cmd + dotenv-safe:在 CI/CD 中通过 env-cmd 注入变量,本地开发用 dotenv-safe 校验。config + 环境变量:config 管理大部分配置,敏感字段通过环境变量注入(通过 custom-environment-variables.json 映射)。⚠️ 避免反模式:不要同时用
dotenv和config加载同一份配置,会导致覆盖逻辑混乱。
| 能力 | dotenv | dotenv-safe | config | env-cmd |
|---|---|---|---|---|
加载 .env 文件 | ✅ | ✅ | ❌ | ✅ (CLI 方式) |
| 必需变量校验 | ❌ | ✅ | ❌ (按需校验) | ❌ |
| 嵌套配置结构 | ❌ | ❌ | ✅ | ❌ |
| 多环境自动切换 | ❌ (需手动) | ❌ (需手动) | ✅ | ❌ (需手动指定) |
| 非 Node.js 适用 | ❌ | ❌ | ❌ | ✅ |
| 是否需代码集成 | ✅ | ✅ | ✅ | ❌ |
dotenv,简单直接。dotenv-safe,安全第一。config,结构清晰。env-cmd,通用灵活。选择工具的本质是权衡简洁性与控制力。没有银弹,只有最适合当前上下文的方案。
选择 config 如果你需要管理复杂的、分层的配置结构,支持多环境(开发、测试、生产)自动切换,并且配置项包含嵌套对象。它适合大型企业级应用,但需注意敏感信息不要硬编码在配置文件中。对于简单场景,它可能过于重量级。
选择 dotenv 如果你只需要最基础的 .env 文件加载功能,项目规模小或前端构建工具(如 Vite)已内置支持。它轻量、无依赖,但缺乏校验机制,不适合对配置完整性要求高的后端服务。
选择 dotenv-safe 如果你在 Node.js 后端项目中使用 .env 文件,且需要确保所有必需的环境变量都已提供。它在启动时强制校验,避免因配置缺失导致运行时错误,是 dotenv 的安全增强版。
选择 env-cmd 如果你需要为非 Node.js 脚本(如 Python、Shell)或临时任务注入环境变量,或者希望在不修改代码的情况下通过命令行切换配置。它作为 CLI 工具使用,不需在代码中引入依赖,但缺乏运行时校验和结构化配置能力。
Node-config organizes hierarchical configurations for your app deployments.
It lets you define a set of default parameters, and extend them for different deployment environments (development, qa, staging, production, etc.).
Configurations are stored in configuration files within your application, and can be overridden and extended by environment variables, command line parameters, or external sources.
This gives your application a consistent configuration interface shared among a growing list of npm modules also using node-config.
The following examples are in JSON format, but configurations can be in other file formats.
Install in your app directory, and edit the default config file.
$ npm install config
$ mkdir config
$ vi config/default.json
{
// Customer module configs
"Customer": {
"dbConfig": {
"host": "localhost",
"port": 5984,
"dbName": "customers"
},
"credit": {
"initialLimit": 100,
// Set low for development
"initialDays": 1
}
}
}
Edit config overrides for production deployment:
$ vi config/production.json
{
"Customer": {
"dbConfig": {
"host": "prod-db-server"
},
"credit": {
"initialDays": 30
}
}
}
Use configs in your code:
const config = require('config');
//...
const dbConfig = config.get('Customer.dbConfig');
db.connect(dbConfig, ...);
if (config.has('optionalFeature.detail')) {
const detail = config.get('optionalFeature.detail');
//...
}
config.get() will throw an exception for undefined keys to help catch typos and missing values.
Use config.has() to test if a configuration value is defined.
Start your app server:
$ export NODE_ENV=production
$ node my-app.js
Running in this configuration, the port and dbName elements of dbConfig
will come from the default.json file, and the host element will
come from the production.json override file.
Type declarations are published under types/ and resolved via typesVersions. Subpath typings are included for config/async, config/defer, config/parser, config/raw, and config/lib/util in addition to the main config entrypoint.
If you still don't see what you are looking for, here are some more resources to check:
node-config contributors.May be freely distributed under the MIT license.
Copyright (c) 2010-2026 Loren West and other contributors