config、dotenv、dotenv-safe 和 envalid 都是用于管理应用程序配置和环境变量的 Node.js 工具,但它们的设计理念和适用场景有明显差异。dotenv 是最基础的工具,用于从 .env 文件加载环境变量到 process.env;dotenv-safe 在其基础上增加了对必需变量的校验;envalid 则提供了一套完整的类型安全、验证和默认值机制,强调运行时配置的可靠性;而 config 采用基于文件的多环境配置方案,适合复杂企业级应用,通过 JSON/YAML/JS 等格式组织不同环境的配置文件。这些工具帮助开发者在不同部署环境中安全、清晰地管理配置,避免硬编码敏感信息或逻辑分支。
在现代 JavaScript 应用中,如何安全、可靠地管理配置和环境变量是一个基础但关键的问题。config、dotenv、dotenv-safe 和 envalid 提供了四种截然不同的思路。本文将从实际工程角度,深入比较它们的核心机制、适用边界和代码实践。
config 使用基于文件的配置体系,按环境划分配置文件。
config/ 目录加载 default.json,再根据 NODE_ENV 覆盖(如 production.json)。// config/default.json
{
"db": {
"host": "localhost",
"port": 5432
},
"apiTimeout": 5000
}
// config/production.json
{
"db": {
"host": "prod-db.example.com"
}
}
// app.js
const config = require('config');
console.log(config.get('db.host')); // 生产环境输出 'prod-db.example.com'
dotenv 从 .env 文件读取键值对,注入到 process.env。
# .env
DB_HOST=localhost
DB_PORT=5432
API_TIMEOUT=5000
// app.js
require('dotenv').config();
console.log(process.env.DB_HOST); // 'localhost'
console.log(typeof process.env.DB_PORT); // 'string' — 注意!
dotenv-safe 在 dotenv 基础上,要求提供 .env.example 作为必需变量模板。
.env 是否包含 .env.example 中定义的所有变量。process.env,但提供更强的存在性保证。# .env.example
DB_HOST=
DB_PORT=
API_TIMEOUT=
// app.js
require('dotenv-safe').config();
// 若 .env 缺少 DB_HOST,程序会抛出错误并退出
envalid 要求显式声明每个环境变量的规范,生成一个干净的配置对象。
process.env,而是返回一个经过验证和类型转换的对象。// config.js
const { cleanEnv, str, num } = require('envalid');
const env = cleanEnv(process.env, {
DB_HOST: str(),
DB_PORT: num(),
API_TIMEOUT: num({ default: 5000 })
});
module.exports = env;
// app.js
const env = require('./config');
console.log(env.DB_HOST); // 'localhost'
console.log(typeof env.DB_PORT); // 'number' — 自动转换!
config 本身不提供任何验证或类型转换。
// config/custom-environment-variables.js
// 仅支持将 process.env 映射到 config 结构,无验证
module.exports = {
db: {
host: 'DB_HOST', // 从 process.env.DB_HOST 读取
port: 'DB_PORT'
}
};
dotenv 完全无验证。
.env 文件缺失或变量未定义,也不会报错,可能导致后续逻辑崩溃。dotenv-safe 仅验证变量是否存在,不验证类型或格式。
DB_PORT=abc,它不会报错,但后续 parseInt(process.env.DB_PORT) 会得到 NaN。envalid 提供全面的运行时验证和类型转换。
str、num、bool、url、email 等验证器。const { cleanEnv, str, url } = require('envalid');
const env = cleanEnv(process.env, {
API_BASE_URL: url(), // 必须是有效 URL
LOG_LEVEL: str({ choices: ['debug', 'info', 'error'] })
});
// 若 API_BASE_URL='not-a-url',启动时抛出:
// EnvVarError: Invalid URL for API_BASE_URL: not-a-url
config 通过 default.json 提供默认值,环境特定文件可覆盖。
dotenv 无内置默认值机制。
const port = process.env.PORT || 3000;dotenv-safe 同样无默认值支持。
.env.example 中列出的变量必须全部存在,无法标记为可选。envalid 显式支持默认值和可选变量。
default 选项提供回退值,未提供且无默认值的变量视为必需。const env = cleanEnv(process.env, {
PORT: num({ default: 3000 }), // 可选,有默认值
DATABASE_URL: str() // 必需
});
config 在配置缺失时可能静默返回 undefined,导致延迟错误。
config.get('non.existent') 返回 undefined,直到使用时才报错。dotenv 和 dotenv-safe 的错误通常发生在应用逻辑层,而非配置加载层。
envalid 坚持“快速失败”原则。
Object.freeze),防止意外修改。在前端项目(如 React、Vue)中,通常需要将环境变量内联到构建产物中。
dotenv 被 Create React App、Vite 等工具链原生支持(自动加载 .env 文件)。envalid 可在构建前的 Node.js 脚本中使用,验证后将结果写入临时文件供构建工具读取。config 和 dotenv-safe 在纯前端项目中较少直接使用,因其依赖 Node.js 文件系统。例如,在 Vite 项目中结合 envalid:
// scripts/validate-env.js
const fs = require('fs');
const { cleanEnv, str } = require('envalid');
const env = cleanEnv(process.env, {
VITE_API_URL: str()
});
// 将验证后的变量写入 vite.config.js 可读取的位置
fs.writeFileSync(
'./src/env-config.js',
`export const envConfig = ${JSON.stringify(env)};`
);
// vite.config.js
import { defineConfig } from 'vite';
import { envConfig } from './src/env-config.js';
export default defineConfig({
define: {
'__APP_CONFIG__': JSON.stringify(envConfig)
}
});
| 特性 | config | dotenv | dotenv-safe | envalid |
|---|---|---|---|---|
| 配置来源 | 多文件(JSON/YAML/JS) | .env 文件 | .env + .env.example | process.env(显式声明) |
| 类型转换 | ❌ | ❌ | ❌ | ✅(自动) |
| 验证能力 | ❌ | ❌ | 存在性检查 | ✅(类型、格式、自定义) |
| 默认值 | 通过 default.json | 手动处理 | ❌ | ✅(声明式) |
| 错误时机 | 延迟(使用时) | 延迟 | 启动时(仅存在性) | 启动时(全面) |
| 前端友好度 | 低(需 Node.js) | 高(工具链集成) | 中 | 中(需构建脚本) |
dotenv 支持即可,保持简单。dotenv-safe,防止部署遗漏。config,利用其多环境文件组织能力。envalid,尤其适合 TypeScript 项目。最终,没有“最好”的工具,只有“最合适”当前项目规模、团队习惯和可靠性要求的方案。
选择 config 如果你的项目需要支持多个复杂环境(如 dev、test、staging、prod),且每个环境有大量结构化配置项。它通过文件系统组织配置,天然支持继承和覆盖,适合大型后端服务或全栈应用。但要注意它不直接处理 .env 文件,也不提供类型验证,需配合其他机制确保配置正确性。
选择 dotenv 如果你只需要简单地从 .env 文件加载键值对到 process.env,且项目对配置验证要求不高。它是生态中最轻量、使用最广泛的方案,适合小型项目或作为其他配置系统的底层依赖。但缺乏对缺失变量或类型错误的防护,生产环境需额外保障。
选择 dotenv-safe 如果你希望在 dotenv 的基础上增加对必需环境变量的检查,防止因遗漏关键配置导致运行时错误。它通过 .env.example 定义模板,启动时校验所有必需变量是否存在。适合中等规模项目,追求比 dotenv 更强的安全性,又不想引入复杂验证逻辑的场景。
选择 envalid 如果你重视类型安全、明确的配置契约和早期失败原则。它强制定义每个环境变量的类型、默认值和验证规则,并在启动时抛出清晰错误。生成的配置对象具有 TypeScript 类型推断支持,极大减少运行时错误。适合对可靠性和可维护性要求高的现代前端或全栈项目。
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