六维教程

Node.js 环境变量与配置

同一个项目,开发机上端口用 3000,服务器上要用 8080,数据库密码也完全不同。把配置写死在代码里,换环境就得改代码,密码还有可能被提交进 git 仓库。这篇讲环境变量的标准用法,用 .env 文件把配置和环境分离。上一篇 Node.js RESTful API 设计 的服务器端口还是硬编码,这篇给它加上配置能力。

process.env

process.env 是 Node 提供的对象,装着当前进程的所有环境变量,直接按名读取。

// env-demo.js
console.log(process.env.PORT);
console.log(process.env.NODE_ENV);

环境变量的值在启动进程时由终端传入,不传就打印 undefined。

# PowerShell 写法,Windows 下用这种
$env:PORT=3000; node env-demo.js

# bash 写法,Linux 和 macOS 用这种
PORT=3000 node env-demo.js

NODE_ENV 是最常用的约定变量,取值一般是 development、production、test 三选一。Express 会按它决定默认行为,比如生产环境下不暴露错误堆栈。注意 NODE_ENV 本身没有默认值,代码里要自己处理缺省情况。

敏感信息红线

数据库密码、API 密钥这类信息,绝对不能写死在代码里,也不能提交进 git。写进代码意味着每个能看仓库的人都能拿到密码,而且提交历史里删都删不干净。

处理方式是把敏感值放进 .env 文件,然后让 git 忽略它。

# .gitignore,追加下面两行
node_modules/
.env

提交一份 .env.example 到仓库,只写变量名和示例值,别人克隆项目后照着它创建自己的 .env。

# .env.example,可以提交到 git
PORT=3000
DB_PASSWORD=换成你自己的密码

dotenv 加载 .env 文件

.env 文件本身不会自动生效,需要 dotenv 包把它读进 process.env。

npm install dotenv
// server.js 顶部
require("dotenv").config();

const port = process.env.PORT;
console.log("端口", port);

.env 文件内容就是一行一个键值对。

# .env
PORT=3000
DB_PASSWORD=my-password

Node 20 也提供了内置的加载方式,不需要装包,启动时加参数即可。

node --env-file=.env server.js

dotenv 适合习惯 npm 生态的团队,–env-file 适合想少装依赖的项目,二选一即可,下面统一用 dotenv。

多环境配置

一套 .env 不够用,开发和生产要分别管理,常见做法是按环境拆文件。

# .env.development,开发环境
PORT=3000
LOG_LEVEL=debug
DB_HOST=localhost
DB_PASSWORD=dev-password

# .env.production,生产环境
PORT=8080
LOG_LEVEL=info
DB_HOST=db.example.com
DB_PASSWORD=prod-password

用 NODE_ENV 决定加载哪个文件。

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

启动时在 npm scripts 里指定 NODE_ENV,package.json 配置如下。

{
  "scripts": {
    "dev": "cross-env NODE_ENV=development node server.js",
    "prod": "cross-env NODE_ENV=production node server.js"
  }
}
npm install cross-env
npm run dev
npm run prod

Windows 的命令行不认 NODE_ENV=xxx node 这种内联写法,cross-env 让同一份 scripts 在 Windows、Linux、macOS 上都能跑。不装 cross-env 的话,PowerShell 里用 $env:NODE_ENV=”production” 手动设置也行。

配置最佳实践

给默认值

不是所有变量都必须从环境读,读不到时给个兜底默认值,本地启动更省心。

const port = process.env.PORT || 3000;
const logLevel = process.env.LOG_LEVEL || "info";

启动时校验必填项

数据库密码这类缺了就跑不起来的配置,启动时先校验,缺了就立刻退出,别等服务运行时才报错。

const required = ["DB_PASSWORD", "DB_HOST"];
for (const key of required) {
  if (!process.env[key]) {
    console.error(`缺少环境变量 ${key}`);
    process.exit(1);
  }
}

常见坑

.env 被提交进 git

忘记在 .gitignore 里写 .env,密码全进仓库。先确认 .gitignore 里有这一行,再用 git status 检查 .env 是否被跟踪。已经提交过的话要改密码,光删文件没用,提交历史里还留着。

Windows 上内联环境变量不生效

PowerShell 里写 NODE_ENV=production node server.js 会直接报错,npm scripts 里也一样。用 cross-env 或者 $env: 前缀,跨平台脚本统一用 cross-env。

dotenv.config() 位置靠后

dotenv.config() 必须在使用环境变量的代码之前执行,放在文件最顶部第一行。require 语句写在中间,后面的代码能读到,前面的就读不到了,顺序错误的表现很隐蔽。

改动 .env 后忘了重启

.env 在进程启动时读取,改完文件必须重新执行 node server.js 才生效。开发模式的热更新能重载代码,但 .env 的改动不一定被重新读取,手动重启最稳。

实践

配置开发和生产两套环境,端口和日志级别不同,启动时输出当前环境信息验证加载结果。

// server.js
const env = process.env.NODE_ENV || "development";
require("dotenv").config({ path: `.env.${env}` });

const express = require("express");

const app = express();
const port = process.env.PORT || 3000;

// 日志级别按环境配置,开发环境打全量请求日志
app.use((req, res, next) => {
  if (process.env.LOG_LEVEL === "debug") {
    console.log(`${new Date().toISOString()} ${req.method} ${req.url}`);
  }
  next();
});

app.get("/", (req, res) => {
  res.json({
    env,
    port,
    dbHost: process.env.DB_HOST,
    message: "配置加载成功",
  });
});

// 启动前校验必填配置
const required = ["DB_HOST", "DB_PASSWORD"];
for (const key of required) {
  if (!process.env[key]) {
    console.error(`缺少必填环境变量 ${key},检查 .env.${env} 文件`);
    process.exit(1);
  }
}

app.listen(port, () => {
  console.log(`服务器已启动,环境 ${env},端口 ${port}`);
});
# 先创建 .env.development 和 .env.production 两个文件
# 再安装依赖和 cross-env
npm install express dotenv cross-env

# 分别启动两套环境
npm run dev
npm run prod

curl 访问首页,看返回的 env、port、dbHost 是不是各自文件里的值。开发环境访问任意路径,终端会输出每一条请求日志,生产环境只输出启动信息。

配置管理到这里就齐了,数据还躺在内存数组里,下一篇 Node.js MySQL 数据库 把数据落进真正的数据库。

上一篇
Node.js RESTful API 设计
下一篇
Node.js MySQL 数据库