上一节我们讨论了后端分层架构和 RESTful API 设计规范,但再好的架构也架不住一个硬编码的数据库密码被提交到 Git 仓库。环境配置看似简单,却直接影响项目的安全、可维护性和部署灵活度。本节我们从最常用的 dotenv 方案开始,逐步过渡到适合中等以上规模项目的配置中心方案。
1. 为什么需要专门的配置管理
无论项目大小,总有一些值会随环境变化:
- 数据库地址、用户名、密码
- Redis 连接串
- 第三方服务 API 密钥
- 日志级别、文件存储路径
- 功能开关(特性标志)
这些值如果在代码中写死,切换环境就需要修改代码重新部署,运维成本高且极易出错。更麻烦的是,敏感信息(密码、密钥)绝对不能出现在源码树中,一旦泄露后果严重。因此,配置管理的第一原则是:代码与配置分离。
Node.js 中,实现代码与配置分离有多种手段,从最简单的环境变量,到 .env 文件,再到专业的配置中心,复杂度和收益逐级递增。
2. dotenv:最轻量级的本地配置方案
dotenv 是 Node.js 生态中最流行的环境变量加载工具。它的工作原理是:从项目根目录的 .env 文件读取键值对,并将其注入到 process.env 中,让程序能像访问系统环境变量一样读取配置。
基本用法
安装:
npm install dotenv
创建一个 .env 文件(务必加入 .gitignore):
# .env
DB_HOST=localhost
DB_PORT=5432
DB_USER=app_admin
DB_PASSWORD=s3cret!value
JWT_SECRET=my-super-secret-key
在应用入口文件的最顶部加载 dotenv:
// app.js 或 main.ts
require('dotenv').config(); // 注意:必须在任何其他模块之前
// 或使用 import
import 'dotenv/config';
const dbHost = process.env.DB_HOST; // 'localhost'
dotenv 默认读取 .env 文件,如果文件不存在或者格式有误,也不会抛出异常(可以通过 dotenv.config({ path: '/custom/path/.env' }) 自定义路径)。这使得它在本地开发时极其方便。
进阶技巧:使用 .env.example
为了保护敏感信息并指导其他开发者,通常会提供一个 .env.example 文件,填写伪值并注释说明,然后将此文件提交到 Git:
# .env.example
# 数据库配置
DB_HOST=localhost
DB_PORT=5432
DB_USER=username
DB_PASSWORD=your_password_here
# JWT 签名密钥
JWT_SECRET=change_me_to_a_random_string
新成员克隆项目后,复制一份并填入真实值即可。
3. 多环境隔离:让配置文件跟随环境走
实际项目中通常有开发(development)、测试(testing)、预发布(staging)和生产(production)等多个环境,它们需要的配置各不相同。最简单的多环境方案是通过 NODE_ENV 环境变量来区分,并配合不同的 .env 文件。
典型目录结构
.env.development
.env.testing
.env.production
.env.example # 提交到仓库
在应用入口按 NODE_ENV 动态加载对应文件:
const envFile = `.env.${process.env.NODE_ENV || 'development'}`;
require('dotenv').config({ path: envFile });
console.log(`Loaded config from ${envFile}`);
然后启动时设置 NODE_ENV:
# 开发环境
NODE_ENV=development node app.js
# 生产环境
NODE_ENV=production node app.js
更安全的生产环境配置:使用系统环境变量
在生产环境中,.env 文件存入磁盘存在安全隐患(服务器被入侵后可能被读取),同时也不便于在容器编排平台(如 Kubernetes)或 CI/CD 中修改。更推荐的做法是直接在运行环境中设置系统环境变量,或者通过平台的安全配置注入。
此时 dotenv 通常只在非生产环境下使用,生产环境直接读取系统变量:
// config.js
const nodeEnv = process.env.NODE_ENV || 'development';
if (nodeEnv !== 'production') {
// 开发、测试环境从 .env 文件加载
require('dotenv').config({ path: `.env.${nodeEnv}` });
}
// 统一导出配置对象
const config = {
db: {
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT, 10) || 5432,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
},
jwtSecret: process.env.JWT_SECRET,
logLevel: process.env.LOG_LEVEL || 'debug',
};
module.exports = config;
这样,开发人员本地只需维护 .env.development,CI 或生产环境通过运维平台注入环境变量,干净且安全。
配置校验
环境变量作为字符串,类型和必填性需要在运行时校验。可以手写检查,也可以使用 joi 或 zod 进行声明式校验:
const { z } = require('zod');
const envSchema = z.object({
NODE_ENV: z.enum(['development', 'testing', 'production']),
DB_HOST: z.string(),
DB_PORT: z.string().transform(Number),
DB_PASSWORD: z.string().min(1),
JWT_SECRET: z.string().min(16),
});
// 校验并导出
const env = envSchema.parse(process.env);
这样应用一启动就会对必要的配置进行验证,避免运行时出现莫名其妙的空值错误。
4. 配置中心:适合多服务、多实例的集中式配置
随着微服务或分布式系统规模增长,每个服务各自维护 .env 文件的模式会暴露出两个致命问题:
- 配置变更困难:数据库密码修改后,需要重启所有依赖服务的实例。
- 配置一致性:不同实例之间可能出现配置漂移。
配置中心就是为了解决这类问题而生。它提供一个集中的配置存储和动态下发机制,服务启动时拉取配置,运行时还能监听配置变化并热更新。
常见的配置中心
- Consul(HashiCorp):支持键值存储、服务发现、健康检查,天然集成配置下发能力。
- Nacos(阿里巴巴):国产开源配置中心,支持配置动态刷新、灰度发布,与 Spring Cloud 等生态融合好,但也提供 Node.js SDK。
- Etcd(CoreOS):分布式键值存储,也可作为配置中心。
- AWS AppConfig / Azure App Configuration:云平台托管配置服务。
- Git 仓库 + 配置文件:将配置文件(JSON/YAML)放在一个独立的 Git 仓库中,通过 CI/CD 拉取并在部署时注入。这属于配置管理的中间方案,适合不愿意引入额外中间件的小团队。
配置中心的工作原理
通常,Node.js 应用在启动时会通过 SDK 连接到配置中心,拉取当前环境的配置(可能带标签、版本号)。同时,SDK 会监听配置中心的变化事件,当管理员修改了配置,所有受影响的服务实例都会在近乎实时的情况下接收到新值,无需重启。
以 Nacos 为例,Node.js 应用可以通过 nacos npm 包实现配置动态刷新:
npm install nacos
// config-center.js
const NacosConfigClient = require('nacos').NacosConfigClient;
const client = new NacosConfigClient({
serverAddr: '127.0.0.1:8848',
namespace: 'dev',
});
async function loadConfig() {
const content = await client.getConfig('my-app-config', 'DEFAULT_GROUP');
return JSON.parse(content);
}
// 启动时加载
loadConfig().then(config => {
// 将配置挂载到全局或注入到各模块
});
// 监听配置变化
client.subscribe({
dataId: 'my-app-config',
group: 'DEFAULT_GROUP',
}, content => {
console.log('配置更新:', content);
// 动态更新内部状态
});
动态配置特别适合管理功能开关(Feature Flag)、限流阈值、日志级别等需要即时生效的参数。对于数据库等需要建立连接池的配置,一般需要设计专门的连接刷新机制。
配置中心方案的取舍
优点:
- 所有配置集中管理,修改后即可生效。
- 支持灰度发布、版本回滚。
- 审计记录、权限控制更完善。
代价:
- 系统引入新的依赖,增加运维复杂度。
- 配置中心的可用性直接影响所有服务(需要集群部署)。
- 并非所有配置都适合动态刷新(如数据库端口),需区分静态配置和动态配置。
对于中小型项目或早期微服务,直接从环境变量或 .env 文件读取配置已经足够。当团队规模超过数十人、服务数量超过几十个、或者有多个部署环境需要频繁切换时,引入配置中心带来的收益才开始大于其成本。
5. 最佳实践总结
无论采用哪种方案,有几个原则需要牢记:
- 绝不在代码中硬编码配置:连默认值也不要写死,而是明确通过配置读取。
- 敏感信息加密或使用密钥管理服务:数据库密码、API 密钥不应裸写在任何地方。云平台可以使用 Secrets Manager 或 Vault 工具。
- 区分静态配置与动态配置:数据库连接信息通常是静态的(启动时设定),而业务参数可能是动态的(运行时可调)。两者处理方式不同。
- 做好配置的版本管理和回滚预案:无论用
.env还是配置中心,配置变更都应有记录,能够快速回退到上一个可用状态。 - 将配置加载逻辑集中封装:在项目中提供一个统一的
config模块,隐藏具体加载细节,便于未来切换方案。
环境配置做得规范,不仅能保护系统安全,还能让团队成员在任何环境下都快速进入开发状态。从 dotenv 到配置中心,选择适合当前项目规模的方案,就是最好的实践。
下一节我们将进入测试体系的构建,看看如何用单元测试、集成测试为项目质量保驾护航。