环境变量配置

环境变量是操作系统级别或应用运行环境中的动态命名值,用于在不修改代码的情况下改变应用的行为。在 Node.js 开发中,环境变量常用于存储敏感信息(如数据库密码、API 密钥)和环境特定的配置(如调试开关、服务端口)。本章将介绍如何有效地管理和使用环境变量。

1. 为什么需要环境变量?

  • 配置与代码分离:避免将敏感信息硬编码在代码中,提高安全性。
  • 环境特定配置:同一份代码可以在开发、测试、生产环境中运行,只需改变环境变量。
  • 便捷性:无需重新部署即可调整参数(如日志级别)。
  • 遵守十二要素应用原则:其中一项就是“将配置存储在环境中”。

2. 在 Node.js 中访问环境变量

Node.js 通过全局对象 process.env 提供对环境变量的访问。process.env 是一个包含所有用户环境变量的对象。例如,读取 PORT 变量:

const port = process.env.PORT || 3000;
console.log(`Server will run on port ${port}`);

直接设置环境变量的方式取决于操作系统(如 Linux 的 export,Windows 的 set),或者在运行 Node.js 命令时指定:

PORT=8080 node app.js

3. 使用 dotenv 管理配置

手动设置大量环境变量很繁琐,推荐使用 dotenv 库从 .env 文件中加载变量。它会将文件中的键值对读取并合并到 process.env 中。

安装:

npm install dotenv

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

# .env
PORT=3000
DB_HOST=localhost
DB_USER=root
DB_PASSWORD=mysecret
API_KEY=abc123

在应用入口(如 app.js)尽早加载 dotenv:

require('dotenv').config();

console.log(process.env.PORT); // 3000

dotenv 会解析文件并设置环境变量,但不会覆盖已有的环境变量(除非设置 override 选项)。

3.1 指定自定义路径

如果 .env 文件不在根目录,可以指定路径:

require('dotenv').config({ path: '/custom/path/.env' });

3.2 在开发与生产环境中使用

通常,.env 文件用于开发环境,生产环境通过系统级环境变量或容器编排工具(如 Docker、Kubernetes)注入变量。可以通过条件加载避免在非开发环境意外使用 .env

if (process.env.NODE_ENV !== 'production') {
  require('dotenv').config();
}

4. 环境变量命名规范

  • 使用大写字母和下划线,例如 DB_HOSTAPI_KEY
  • 对于布尔值,可以使用 1/0true/false 字符串,并在代码中转换。
  • 避免使用特殊字符和空格。

5. 环境变量验证

在生产环境中,缺少关键环境变量可能导致应用启动失败或运行时错误。建议在启动时验证必要变量是否存在。可以使用 joi 或自定义函数。

const requiredEnv = ['DB_HOST', 'DB_USER', 'DB_PASSWORD'];
requiredEnv.forEach(key => {
  if (!process.env[key]) {
    console.error(`缺少必需的环境变量: ${key}`);
    process.exit(1);
  }
});

或者使用 envalid 库:

npm install envalid
const { cleanEnv, str, port } = require('envalid');

const env = cleanEnv(process.env, {
  NODE_ENV: str({ choices: ['development', 'test', 'production'] }),
  PORT: port({ default: 3000 }),
  DB_HOST: str(),
  DB_PASSWORD: str()
});

console.log(env.DB_HOST); // 经过验证和类型转换

6. 在 Express 应用中使用环境变量

环境变量在 Express 应用中非常常见,例如监听端口、数据库连接、JWT 密钥、API 端点等。

const express = require('express');
require('dotenv').config();

const app = express();
const port = process.env.PORT || 3000;
const dbConfig = {
  host: process.env.DB_HOST,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME
};

app.get('/', (req, res) => {
  res.send('Hello World');
});

app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

7. 不同环境的配置文件

有时你可能需要针对不同环境加载不同的配置。常见做法是使用多个 .env 文件,如 .env.development.env.production,然后根据 NODE_ENV 加载对应的文件。

const envFile = `.env.${process.env.NODE_ENV || 'development'}`;
require('dotenv').config({ path: envFile });

但请注意,将敏感信息存储在文件中仍需谨慎,生产环境应优先使用系统环境变量。

8. 安全最佳实践

  • 永远不要将 .env 文件提交到版本控制系统(如 Git)。在 .gitignore 中添加 .env.env.*,但可以提交一个示例文件 .env.example,包含必要键名和占位值。
  • 限制环境变量的作用范围:不要在日志中输出环境变量,避免泄露。
  • 使用最小权限原则:为服务账户分配最小的数据库权限。
  • 生产环境使用强密钥:通过安全渠道注入环境变量(如 Docker secrets、Kubernetes secrets、AWS Secrets Manager)。
  • 定期轮换密钥:尤其是数据库密码、API 密钥等敏感信息。

9. 示例:一个包含验证的完整配置模块

创建一个 config.js 模块,统一管理环境变量并提供默认值:

// config.js
require('dotenv').config();

const env = process.env;

const config = {
  nodeEnv: env.NODE_ENV || 'development',
  port: env.PORT || 3000,
  database: {
    host: env.DB_HOST || 'localhost',
    port: env.DB_PORT || 5432,
    name: env.DB_NAME || 'myapp',
    user: env.DB_USER,
    password: env.DB_PASSWORD
  },
  jwt: {
    secret: env.JWT_SECRET,
    expiresIn: env.JWT_EXPIRES_IN || '7d'
  },
  redis: {
    url: env.REDIS_URL
  }
};

// 验证必需变量
if (!config.database.user || !config.database.password) {
  throw new Error('数据库用户名和密码必须配置');
}
if (!config.jwt.secret) {
  throw new Error('JWT_SECRET 必须配置');
}

module.exports = config;

然后在应用中使用:

const config = require('./config');
console.log(config.database.host);

10. 在测试中使用环境变量

测试环境下可以动态设置环境变量,例如使用 cross-env 包确保跨平台兼容:

npm install --save-dev cross-env

然后在 package.json 的 scripts 中:

"scripts": {
  "test": "cross-env NODE_ENV=test jest"
}

也可以在测试代码中使用 process.env 赋值,但注意会污染进程环境,建议使用后恢复。

11. 常见问题

  • 环境变量未生效:检查 .env 文件路径是否正确,确保 dotenv.config() 在访问变量前调用。
  • 变量中包含特殊字符:可以使用引号包裹值,如 PASSWORD="pass word"
  • 布尔值解析:环境变量都是字符串,需要手动转换为布尔值:const debug = process.env.DEBUG === 'true'
  • 数字类型:同样需要转换,如 const port = parseInt(process.env.PORT, 10)
总结: 环境变量是实现配置与代码分离的关键。通过 dotenv 管理开发环境配置,在生产环境利用系统级注入,结合验证和最佳实践,可以构建安全、灵活且易于维护的 Node.js 应用。下一章我们将深入探讨 Node.js 的性能优化技巧。