{T}

Node.js 环境变量管理

概述

环境变量是操作系统中用于存储配置信息的动态值,它们可以在进程运行时被访问和修改。在 Node.js 中,环境变量通过 process.env 对象访问

为什么需要环境变量管理?

问题传统硬编码方式环境变量方式
配置灵活性修改需重新部署无需重新部署
安全性敏感信息暴露在代码中敏感信息隔离
多环境支持维护多份代码同一代码适配多环境
团队协作配置冲突频发各自独立配置

核心工具介绍

  • dotenv:从 .env 文件加载环境变量到 process.env
  • cross-env:跨平台设置命令行环境变量

适用场景

  • 多环境部署:开发、测试、生产环境配置隔离
  • 敏感信息保护:数据库密码、API 密钥等不在代码中硬编码
  • 配置热更新:无需修改代码即可调整配置
  • CI/CD 集成:自动化流程中的环境配置管理

系统架构

环境变量流转架构

code
┌─────────────────────────────────────────────────────────────────┐
│                    环境变量管理架构                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐     │
│  │  .env 文件   │    │  命令行参数  │    │  系统环境变量 │     │
│  │  (dotenv)    │    │  (cross-env) │    │  (OS)        │     │
│  └──────┬───────┘    └──────┬───────┘    └──────┬───────┘     │
│         │                   │                   │              │
│         └───────────────────┼───────────────────┘              │
│                             ▼                                  │
│                  ┌─────────────────────┐                       │
│                  │   process.env       │                       │
│                  │   (Node.js 进程)    │                       │
│                  └──────────┬──────────┘                       │
│                             │                                  │
│         ┌───────────────────┼───────────────────┐              │
│         ▼                   ▼                   ▼              │
│  ┌─────────────┐     ┌─────────────┐     ┌─────────────┐      │
│  │ 数据库配置  │     │ API 密钥    │     │ 服务端口    │      │
│  │ DB_HOST     │     │ API_KEY     │     │ PORT        │      │
│  │ DB_PORT     │     │ SECRET      │     │ HOST        │      │
│  └─────────────┘     └─────────────┘     └─────────────┘      │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

优先级顺序

code
命令行参数 (cross-env) > 系统环境变量 > .env 文件 (dotenv) > 默认值
        (最高优先级)                              (最低优先级)

环境变量基础

process.env 对象

javascript
// process.env 是一个对象,存储所有环境变量
console.log(process.env.NODE_ENV);  // 'development' 或 'production'
console.log(process.env.PATH);      // 系统路径
console.log(process.env.HOME);      // 用户主目录

// 设置环境变量(仅在当前进程有效)
process.env.MY_VAR = 'my_value';

// 删除环境变量
delete process.env.MY_VAR;

操作系统层面的环境变量

bash
# macOS/Linux - 临时设置
export NODE_ENV=production
node app.js

# macOS/Linux - 一次性使用
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 的原因。


dotenv 详解

安装

bash
npm install dotenv
# 或
yarn add dotenv
# 或
pnpm add dotenv

基本使用

1. 创建 .env 文件

在项目根目录创建 .env 文件:

bash
# 服务器配置
PORT=3000
HOST=localhost
NODE_ENV=development

# 数据库配置
DB_HOST=localhost
DB_PORT=5432
DB_NAME=myapp_dev
DB_USER=admin
DB_PASS=password123

# 第三方服务
API_KEY=your_api_key_here
SECRET_KEY=your_secret_key_here

2. 在应用中加载

javascript
// 方式一:推荐 - 尽早加载
import dotenv from 'dotenv';
dotenv.config();

// 方式二:指定文件路径
dotenv.config({ path: '.env.production' });

// 方式三:使用 Node.js -r 参数(推荐用于生产环境)
// node -r dotenv/config app.js

3. 使用环境变量

javascript
import dotenv from 'dotenv';
dotenv.config();

const config = {
  port: process.env.PORT || 3000,
  host: process.env.HOST || 'localhost',
  database: {
    host: process.env.DB_HOST,
    port: parseInt(process.env.DB_PORT || '5432'),
    name: process.env.DB_NAME,
    user: process.env.DB_USER,
    password: process.env.DB_PASS,
  },
  api: {
    key: process.env.API_KEY,
    secret: process.env.SECRET_KEY,
  }
};

console.log(`Server running on ${config.host}:${config.port}`);

API 接口说明

dotenv.config(options)

加载 .env 文件到 process.env

javascript
/**
 * @param {Object} options - 配置选项
 * @param {string} [options.path='.env'] - .env 文件路径
 * @param {string} [options.encoding='utf8'] - 文件编码
 * @param {boolean} [options.debug=false] - 启用调试日志
 * @param {boolean} [options.override=false] - 是否覆盖已存在的环境变量
 * @returns {Object} { parsed: { ... }, error: null } 或 { parsed: undefined, error: Error }
 */

// 基本使用
const result = dotenv.config();
if (result.error) {
  throw result.error;
}
console.log(result.parsed);  // { PORT: '3000', ... }

// 自定义路径
dotenv.config({ path: '.env.production' });

// 多文件加载(后面的优先级更高)
dotenv.config({ path: '.env' });
dotenv.config({ path: `.env.${process.env.NODE_ENV}` });

dotenv.parse(content)

解析环境变量字符串。

javascript
/**
 * @param {string} content - .env 文件内容字符串
 * @returns {Object} 解析后的键值对对象
 */

const envConfig = dotenv.parse('PORT=3000\nHOST=localhost');
console.log(envConfig);  // { PORT: '3000', HOST: 'localhost' }

// 从 Buffer 解析
const buf = Buffer.from('API_KEY=abc123');
const config = dotenv.parse(buf);
console.log(config);  // { API_KEY: 'abc123' }

dotenv.populate(target, parsed, options)

将解析的环境变量填充到目标对象。

javascript
/**
 * @param {Object} target - 目标对象(通常是 process.env)
 * @param {Object} parsed - 解析后的键值对
 * @param {Object} [options] - 配置选项
 * @param {boolean} [options.override=false] - 是否覆盖已存在的变量
 */

const parsed = { PORT: '4000', HOST: '0.0.0.0' };
dotenv.populate(process.env, parsed, { override: true });

配置参数详解

参数类型默认值说明
pathstring.env环境变量文件路径
encodingstringutf8文件编码格式
debugbooleanfalse输出调试信息到控制台
overridebooleanfalse是否覆盖已存在的环境变量

示例:多环境配置

javascript
// config.js
import dotenv from 'dotenv';
import path from 'path';

// 根据 NODE_ENV 加载不同配置文件
const env = process.env.NODE_ENV || 'development';

const envPath = path.resolve(process.cwd(), `.env.${env}`);

// 加载通用配置
dotenv.config({ path: '.env' });

// 加载环境特定配置(覆盖通用配置)
dotenv.config({ path: envPath, override: true });

console.log(`Loaded config for ${env} environment`);
bash
# 项目结构
.env                 # 通用配置
.env.development     # 开发环境配置
.env.test            # 测试环境配置
.env.production      # 生产环境配置

.env 文件规则

bash
# 基本格式:KEY=VALUE
PORT=3000

# 多行值(使用双引号包裹)
PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nMIIE...\n-----END RSA PRIVATE KEY-----"

# 注释(以 # 开头)
# 这是数据库配置
DB_HOST=localhost

# 空格处理
# VALUE 会被解析为 "value with spaces"
MESSAGE=value with spaces

# 等号在值中
CONNECTION_STRING=postgresql://user:pass@host:5432/db?ssl=true

# 变量展开(需要 dotenv-expand)
DATABASE_URL=postgresql://localhost:5432/mydb
TEST_DATABASE_URL=${DATABASE_URL}_test

dotenv-expand(变量展开)

bash
npm install dotenv-expand
javascript
import dotenv from 'dotenv';
import dotenvExpand from 'dotenv-expand';

const env = dotenv.config();
dotenvExpand.expand(env);
bash
# .env 文件
BASE_URL=https://api.example.com
API_ENDPOINT=${BASE_URL}/v1
FULL_URL=${API_ENDPOINT}/users

cross-env 详解

安装

bash
npm install --save-dev cross-env
# 或
yarn add --dev cross-env
# 或
pnpm add -D cross-env

基本使用

package.json 脚本配置

json
{
  "scripts": {
    "start": "cross-env NODE_ENV=production node app.js",
    "dev": "cross-env NODE_ENV=development PORT=3000 node app.js",
    "test": "cross-env NODE_ENV=test jest",
    "build": "cross-env NODE_ENV=production webpack --config webpack.config.js"
  }
}

命令行使用

bash
# 设置单个环境变量
npx cross-env NODE_ENV=production node app.js

# 设置多个环境变量
npx cross-env NODE_ENV=production PORT=8080 HOST=0.0.0.0 node app.js

# 带引号的值(包含空格)
npx cross-env APP_NAME="My Application" node app.js

# 跨平台设置 PATH
npx cross-env PATH=./bin:$PATH node app.js

与 dotenv 结合使用

json
{
  "scripts": {
    "start": "cross-env NODE_ENV=production node -r dotenv/config app.js",
    "dev": "cross-env NODE_ENV=development node -r dotenv/config app.js",
    "test": "cross-env NODE_ENV=test node -r dotenv/config jest"
  }
}
javascript
// app.js
// 通过 -r dotenv/config 已自动加载 .env 文件
console.log('NODE_ENV:', process.env.NODE_ENV);  // 来自 cross-env
console.log('DB_HOST:', process.env.DB_HOST);    // 来自 .env 文件

cross-env 配置参数

参数说明示例
VAR=value设置环境变量cross-env NODE_ENV=production
--分隔环境变量和命令cross-env NODE_ENV=prod -- node app.js
"value with spaces"包含空格的值cross-env MSG="Hello World"

高级示例

json
{
  "scripts": {
    "build:analyze": "cross-env NODE_ENV=production ANALYZE=true webpack",
    "build:dev": "cross-env NODE_ENV=development webpack",
    "start:prod": "cross-env NODE_ENV=production PORT=80 HOST=0.0.0.0 node app.js",
    "test:coverage": "cross-env NODE_ENV=test CI=true jest --coverage"
  }
}

工具对比分析

功能对比表

特性dotenvcross-env
用途.env 文件加载环境变量跨平台设置命令行环境变量
安装依赖dotenv (运行时依赖)cross-env (开发依赖)
配置方式.env 文件 + 应用中调用直接在 npm 脚本或命令行中设置
跨平台支持✅ 是✅ 是
持久化存储✅ 是(文件)❌ 否(仅运行时)
变量覆盖可配置自动覆盖
适用场景配置文件加载(所有环境)脚本运行时设置环境变量

工作流程对比

code
dotenv 工作流程:
┌──────────┐    dotenv.config()    ┌──────────────┐
│ .env 文件│ ──────────────────►  │ process.env  │
└──────────┘                       └──────────────┘
                                          │
                                          ▼
                                   ┌──────────────┐
                                   │ 应用读取配置 │
                                   └──────────────┘

cross-env 工作流程:
┌──────────────┐  cross-env ...  ┌──────────────┐
│ npm scripts  │ ─────────────► │ process.env  │
└──────────────┘                 └──────────────┘
                                        │
                                        ▼
                                 ┌──────────────┐
                                 │ 应用执行     │
                                 └──────────────┘

选择建议

code
┌─────────────────────────────────────────────────────────────┐
│                     使用场景决策树                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  需要管理环境变量?                                           │
│         │                                                   │
│         ├─ 是 ──► 是否需要跨平台?                            │
│         │              │                                    │
│         │              ├─ 是 ──► 使用 cross-env              │
│         │              │                                    │
│         │              └─ 否 ──► 是否需要持久化配置?          │
│         │                              │                    │
│         │                              ├─ 是 ──► dotenv      │
│         │                              │                    │
│         │                              └─ 否 ──► 直接设置    │
│         │                                                   │
│         └─ 推荐组合 ──► dotenv + cross-env                   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

最佳实践

项目结构

code
my-project/
├── .env                    # 通用配置(不提交到 Git)
├── .env.example            # 示例配置(提交到 Git)
├── .env.development        # 开发环境配置
├── .env.test               # 测试环境配置
├── .env.production         # 生产环境配置
├── .gitignore              # 忽略 .env 文件
├── config/
│   ├── index.js            # 配置管理入口
│   └── validate.js         # 配置验证
└── package.json

配置管理模块

javascript
// config/index.js
import dotenv from 'dotenv';
import path from 'path';

// 加载环境变量
const nodeEnv = process.env.NODE_ENV || 'development';
dotenv.config({ path: path.resolve(process.cwd(), `.env.${nodeEnv}`) });

// 配置验证
function validateConfig(config) {
  const required = ['DB_HOST', 'DB_NAME', 'JWT_SECRET'];
  const missing = required.filter(key => !config[key]);
  
  if (missing.length > 0) {
    throw new Error(`缺少必需的环境变量: ${missing.join(', ')}`);
  }
}

// 配置对象
const config = {
  env: nodeEnv,
  port: parseInt(process.env.PORT || '3000', 10),
  host: process.env.HOST || 'localhost',
  
  database: {
    host: process.env.DB_HOST,
    port: parseInt(process.env.DB_PORT || '5432', 10),
    name: process.env.DB_NAME,
    user: process.env.DB_USER,
    password: process.env.DB_PASSWORD,
  },
  
  jwt: {
    secret: process.env.JWT_SECRET,
    expiresIn: process.env.JWT_EXPIRES_IN || '7d',
  },
  
  redis: {
    host: process.env.REDIS_HOST || 'localhost',
    port: parseInt(process.env.REDIS_PORT || '6379', 10),
    password: process.env.REDIS_PASSWORD,
  },
  
  logs: {
    level: process.env.LOG_LEVEL || 'info',
  },
};

// 验证配置
validateConfig(config);

export default config;

package.json 脚本配置

json
{
  "scripts": {
    "start": "cross-env NODE_ENV=production node -r dotenv/config src/app.js",
    "dev": "cross-env NODE_ENV=development nodemon -r dotenv/config src/app.js",
    "test": "cross-env NODE_ENV=test jest --coverage",
    "test:watch": "cross-env NODE_ENV=test jest --watch",
    "build": "cross-env NODE_ENV=production webpack"
  }
}

TypeScript 支持

typescript
// src/types/env.d.ts
export interface EnvConfig {
  NODE_ENV: 'development' | 'test' | 'production';
  PORT?: string;
  DB_HOST: string;
  DB_PORT?: string;
  DB_NAME: string;
  DB_USER: string;
  DB_PASSWORD: string;
  JWT_SECRET: string;
}

declare global {
  namespace NodeJS {
    interface ProcessEnv extends EnvConfig {}
  }
}

export {};
typescript
// src/config/index.ts
import dotenv from 'dotenv';
import path from 'path';

dotenv.config({ 
  path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV}`) 
});

const config = {
  port: parseInt(process.env.PORT || '3000', 10),
  database: {
    host: process.env.DB_HOST!,
    port: parseInt(process.env.DB_PORT || '5432', 10),
    name: process.env.DB_NAME!,
    user: process.env.DB_USER!,
    password: process.env.DB_PASSWORD!,
  },
  jwt: {
    secret: process.env.JWT_SECRET!,
    expiresIn: process.env.JWT_EXPIRES_IN || '7d',
  },
} as const;

export default config;

安全指南

.gitignore 配置

gitignore
# 环境变量文件
.env
.env.local
.env.*.local
.env.development
.env.test
.env.production

# 保留示例文件
!.env.example

.env.example 示例文件

bash
# 服务器配置
PORT=3000
HOST=localhost
NODE_ENV=development

# 数据库配置
DB_HOST=localhost
DB_PORT=5432
DB_NAME=your_database_name
DB_USER=your_username
DB_PASSWORD=your_password

# JWT 配置
JWT_SECRET=your_jwt_secret_here
JWT_EXPIRES_IN=7d

# Redis 配置
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=

# 第三方 API
API_KEY=your_api_key_here

敏感信息处理最佳实践

javascript
// ❌ 错误:硬编码敏感信息
const dbPassword = 'mypassword123';

// ✅ 正确:从环境变量读取
const dbPassword = process.env.DB_PASSWORD;

// ✅ 更好:验证并设置默认值
const dbPassword = process.env.DB_PASSWORD || (() => {
  if (process.env.NODE_ENV === 'development') {
    console.warn('警告:使用默认数据库密码');
    return 'dev_password';
  }
  throw new Error('DB_PASSWORD 环境变量未设置');
})();

生产环境安全检查

javascript
// config/security.js
export function validateProductionConfig() {
  if (process.env.NODE_ENV === 'production') {
    const checks = [
      { key: 'DB_PASSWORD', message: '数据库密码未设置' },
      { key: 'JWT_SECRET', message: 'JWT 密钥未设置' },
      { key: 'API_KEY', message: 'API 密钥未设置' },
    ];
    
    const errors = checks
      .filter(check => !process.env[check.key])
      .map(check => check.message);
    
    if (errors.length > 0) {
      throw new Error(`生产环境配置错误:\n${errors.join('\n')}`);
    }
    
    // 检查是否使用弱密钥
    const weakSecrets = ['password', 'secret', '123456', 'admin'];
    if (weakSecrets.some(weak => process.env.JWT_SECRET?.includes(weak))) {
      throw new Error('JWT_SECRET 使用了弱密钥,请更换');
    }
  }
}

Docker 环境配置

dockerfile
# Dockerfile
FROM node:18-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .

# 不复制 .env 文件,通过 docker-compose 或 k8s 注入
CMD ["node", "src/app.js"]
yaml
# docker-compose.yml
version: '3.8'

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - PORT=3000
    env_file:
      - .env.production
    # 或使用 secrets
    secrets:
      - db_password

secrets:
  db_password:
    file: ./secrets/db_password.txt

常见问题解答

Q1: .env 文件不生效怎么办?

问题: 配置了 .env 文件但 process.env 中读取不到变量。

解决方案:

javascript
// 1. 确保 dotenv.config() 在最前面调用
import dotenv from 'dotenv';
dotenv.config();  // 必须在使用环境变量之前

import express from 'express';  // 在 dotenv 之后导入
javascript
// 2. 检查文件路径
import dotenv from 'dotenv';
import path from 'path';

const result = dotenv.config({ 
  path: path.resolve(__dirname, '../.env') 
});

if (result.error) {
  console.error('加载 .env 文件失败:', result.error);
}
javascript
// 3. 启用调试模式
dotenv.config({ debug: true });

Q2: 环境变量是字符串类型,如何转换类型?

问题: process.env.PORT 是字符串,需要转换为数字。

解决方案:

javascript
// 数字转换
const port = parseInt(process.env.PORT || '3000', 10);

// 布尔值转换
const debug = process.env.DEBUG === 'true';

// 数组转换
const hosts = (process.env.ALLOWED_HOSTS || '').split(',');

// JSON 对象转换
const config = JSON.parse(process.env.CONFIG_JSON || '{}');

// 封装类型转换函数
function getEnvNumber(key: string, defaultValue: number): number {
  const value = process.env[key];
  return value ? parseInt(value, 10) : defaultValue;
}

function getEnvBoolean(key: string, defaultValue = false): boolean {
  const value = process.env[key];
  if (value === undefined) return defaultValue;
  return value === 'true' || value === '1';
}

function getEnvArray(key: string, separator = ','): string[] {
  const value = process.env[key];
  return value ? value.split(separator).filter(Boolean) : [];
}

Q3: 如何在多环境中管理不同的配置?

解决方案:

javascript
// config/index.js
import dotenv from 'dotenv';
import path from 'path';

const env = process.env.NODE_ENV || 'development';

// 加载优先级:.env.{NODE_ENV}.local > .env.{NODE_ENV} > .env.local > .env
const envFiles = [
  `.env.${env}.local`,
  `.env.${env}`,
  '.env.local',
  '.env',
];

envFiles.forEach(file => {
  const result = dotenv.config({ 
    path: path.resolve(process.cwd(), file) 
  });
  if (!result.error) {
    console.log(`Loaded ${file}`);
  }
});

Q4: 如何处理敏感信息?

解决方案:

javascript
// 1. 使用环境变量
const password = process.env.DB_PASSWORD;

// 2. 生产环境不使用 .env 文件
// 通过 CI/CD 或容器编排工具注入

// 3. 使用 Vault 等密钥管理服务
import vault from 'node-vault';

const client = vault({
  endpoint: process.env.VAULT_ADDR,
  token: process.env.VAULT_TOKEN,
});

const secret = await client.read('secret/database');
const dbPassword = secret.data.password;

// 4. 加密 .env 文件
// 使用 dotenv-vault 或 sops 等工具

Q5: cross-env 和 dotenv 的执行顺序是怎样的?

优先级:

code
cross-env 设置的变量 > 系统环境变量 > .env 文件 > 默认值

示例:

json
{
  "scripts": {
    "start": "cross-env PORT=8080 node -r dotenv/config app.js"
  }
}
javascript
// app.js
console.log(process.env.PORT);  // 8080 (cross-env 优先)
bash
# .env 文件
PORT=3000

最终 PORT 值为 8080,因为 cross-env 设置的变量优先级高于 .env 文件。

Q6: 如何验证必需的环境变量?

解决方案:

javascript
// config/validate.js
const requiredEnvVars = [
  'DB_HOST',
  'DB_NAME',
  'JWT_SECRET',
  'API_KEY',
];

export function validateEnv() {
  const missing = requiredEnvVars.filter(key => !process.env[key]);
  
  if (missing.length > 0) {
    console.error('❌ 缺少必需的环境变量:');
    missing.forEach(key => console.error(`   - ${key}`));
    console.error('\n请检查 .env 文件或环境变量配置');
    process.exit(1);
  }
  
  console.log('✅ 环境变量验证通过');
}
javascript
// app.js
import dotenv from 'dotenv';
dotenv.config();

import { validateEnv } from './config/validate';
validateEnv();

// 启动应用...

Q7: 测试环境如何处理环境变量?

解决方案:

javascript
// jest.config.js
module.exports = {
  testEnvironment: 'node',
  setupFiles: ['<rootDir>/tests/setup.js'],
};

// tests/setup.js
import dotenv from 'dotenv';

dotenv.config({ path: '.env.test' });

// 或使用 jest 环境变量
process.env.NODE_ENV = 'test';
process.env.DB_NAME = 'test_database';
json
// package.json
{
  "scripts": {
    "test": "cross-env NODE_ENV=test jest",
    "test:coverage": "cross-env NODE_ENV=test jest --coverage"
  }
}

总结

环境变量管理是 Node.js 应用的核心配置方案。通过合理使用 dotenvcross-env,可以实现:

  • ✅ 配置与代码分离
  • ✅ 多环境灵活切换
  • ✅ 敏感信息安全保护
  • ✅ 跨平台一致性支持

推荐实践:

  1. 开发环境使用 .env 文件管理配置
  2. 生产环境通过 CI/CD 或容器编排工具注入
  3. 使用 .env.example 作为配置模板
  4. 启动时验证必需的环境变量
  5. 敏感信息使用密钥管理服务

相关文档: