CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Node.js 后端入门/课时/环境变量与配置
RawBlame
课时目录12 节

Node.js 后端入门

  1. 01Node.js 是什么
  2. 02运行时与 process
  3. 03模块与 npm
  4. 04文件系统与 path
  5. 05http 模块
  6. 06Express 入门
  7. 07事件循环与异步
  8. 08环境变量与配置
  9. 09Streams 基础
  10. 10错误处理
  11. 11测试 node:test
  12. 12综合练习与选型

环境变量与配置

dotenv、process.env、开发/生产配置分离。

25 分钟

环境变量与配置

这一课你将能做到

  1. 用 process.env 读取环境变量,并说明它「全字符串、按进程隔离」的两个特性
  2. 用 node --env-file 或 dotenv 加载 .env 文件,把密钥、端口从代码里彻底挪出去
  3. 搭一个按环境(dev/test/prod)切换配置的 config 模块,并知道哪些文件绝不能提交 git

为什么要学

代码里写死 const dbPassword = 'admin123' 会发生什么?提交 git 就是公开处刑——爬虫扫到你的仓库,密码几小时内就会被尝试连接。行业解法是 12-Factor 的配置分离原则:代码同一份,配置按环境注入,密钥只存在于部署环境里。

人话:环境变量就是「操作系统级别的小储物柜」,你启动进程前把值放进去,进程用 process.env 取。开发时值在 .env 文件里(不提交 git),生产时值在部署平台的配置面板里。这课内容不多,但它是所有真实项目的「入场券」——不会这课,项目连安全地放到 GitHub 上都做不到。

先建立图像

┌─ 开发环境 ─────────────────────────────┐
│  .env 文件(git 忽略)                  │
│    PORT=3000                           │
│    DB_HOST=localhost                   │
│  启动: node --env-file=.env server.js  │
│         └──► 注入 process.env          │
└────────────────────────────────────────┘
┌─ 生产环境 ─────────────────────────────┐
│  部署平台配置面板 / CI 变量 / K8s Secret│
│  (同样注入 process.env,代码零改动)    │
└────────────────────────────────────────┘

代码里只写: const port = Number(process.env.PORT ?? 3000)

同一份代码,值从外面来——这就是「配置与代码分离」。

跟着做

步骤 1:认识 process.env

新建 node-env 目录,npm init -y,新建 show-env.js:

// show-env.js
console.log('系统 PATH 存在吗:', Boolean(process.env.PATH));
console.log('当前用户(Windows):', process.env.USERNAME);
console.log('自定义变量 MY_FLAG:', process.env.MY_FLAG);   // 没设置就是 undefined
console.log('PORT 原始值:', JSON.stringify(process.env.PORT));

终端里先注入再运行(PowerShell):

$env:PORT="3000"; node show-env.js
# 自定义变量 MY_FLAG: undefined
# PORT 原始值: "3000"      ← 注意是字符串,带引号!

Linux/macOS(bash)写法:

PORT=3000 MY_FLAG=1 node show-env.js

两个铁律当场验证:没设置的变量是 undefined;设置的值永远是字符串("3000" 不是 3000)。

步骤 2:写一个健壮的取值工具

新建 env.js:

// env.js —— 把"取环境变量 + 类型转换 + 默认值"收编成三个小函数
function envStr(key, defaultValue) {
  const v = process.env[key];
  return v === undefined || v === '' ? defaultValue : v;
}

function envInt(key, defaultValue) {
  const v = Number.parseInt(process.env[key] ?? '', 10);
  return Number.isNaN(v) ? defaultValue : v;
}

function envBool(key, defaultValue) {
  const v = process.env[key];
  if (v === undefined) return defaultValue;
  return v === '1' || v.toLowerCase() === 'true';
}

module.exports = { envStr, envInt, envBool };

使用示范 check-config.js:

// check-config.js
const { envStr, envInt, envBool } = require('./env');

const port = envInt('PORT', 3000);
const host = envStr('DB_HOST', 'localhost');
const debug = envBool('DEBUG', false);

console.log({ port, host, debug });
node check-config.js
# { port: 3000, host: 'localhost', debug: false }

$env:PORT="4000"; $env:DEBUG="true"; node check-config.js
# { port: 4000, host: 'localhost', debug: true }

直接在业务代码里到处写 process.env.XXX 是配置散落的开始;收敛到一个模块,类型和默认值就都有了唯一出处。

步骤 3:用 --env-file 加载 .env(Node 20.6+ 内置)

新建 .env 文件(项目根目录):

PORT=3000
DB_HOST=localhost
DB_USER=dev_user
DB_PASSWORD=dev-secret-do-not-commit
DEBUG=true

新建 server.js 验证:

// server.js
const { envInt, envStr, envBool } = require('./env');

const port = envInt('PORT', 3000);
console.log(`启动于端口 ${port}`);
console.log('DB 地址:', envStr('DB_HOST', 'unset'));
console.log('DEBUG:', envBool('DEBUG', false));
node --env-file=.env server.js
# 启动于端口 3000
# DB 地址: localhost
# DEBUG: true

--env-file 是 Node 20.6+ 的内置能力,零依赖。补充两个实用变体:

node --env-file=.env.development server.js   # 指定文件
node --env-file-if-missing=.env server.js    # 文件不存在不报错(进 CI 用)

老项目里常见 dotenv 包(npm i dotenv 后在入口第一行 require('dotenv').config()),效果相同——读它就是认识老代码。

步骤 4:把 .env 挡在 git 门外

.env 含密钥,必须让 git 无视它。新建 .gitignore:

node_modules/
.env
.env.local
.env.*.local

同时给协作者一个模板 .env.example(提交 git,值全用占位符):

PORT=3000
DB_HOST=localhost
DB_USER=
DB_PASSWORD=
DEBUG=false
git check-ignore .env    # 输出 .env 说明已被忽略,配置生效

流程:新人克隆项目 → 复制 .env.example 为 .env → 填上本地值 → 跑起来。.env.example 是团队约定,.env 是个人秘密,两者永远分家。

步骤 5:按环境组织的 config 模块

真实项目不只有一个环境。做法:.env 提供基础值,代码里按 NODE_ENV 做环境分支。

新建 config.js:

// config.js —— 全项目唯一的配置出口
const { envStr, envInt, envBool } = require('./env');

const nodeEnv = envStr('NODE_ENV', 'development');
const isProd = nodeEnv === 'production';

const defaults = {
  development: { logLevel: 'debug', dbHost: 'localhost', dbPort: 3306 },
  test:        { logLevel: 'warn',  dbHost: 'localhost', dbPort: 3307 },
  production:  { logLevel: 'info',  dbHost: '',            dbPort: 3306 },
};
const d = defaults[nodeEnv] ?? defaults.development;   // 未知环境回退到 development

const conf = {
  nodeEnv,
  isProd,
  port: envInt('PORT', 3000),
  logLevel: envStr('LOG_LEVEL', d.logLevel),
  db: {
    host: envStr('DB_HOST', d.dbHost),
    port: envInt('DB_PORT', d.dbPort),
    user: envStr('DB_USER', ''),
    password: envStr('DB_PASSWORD', ''),
  },
  debug: envBool('DEBUG', !isProd),
};

module.exports = Object.freeze(conf);   // 冻结,防止运行期被意外改写

使用:

// server.js 追加
const config = require('./config');
console.log(`环境=${config.nodeEnv} 日志级别=${config.logLevel} DB=${config.db.host}:${config.db.port}`);
node --env-file=.env server.js
# 环境=development 日志级别=debug DB=localhost:3306

两个设计点:Object.freeze 让配置只读;生产环境敏感项强制从 env 取值(默认值为空串),起服务时缺失就应立刻报错,而不是用兜底值带病上线。

讲清楚要点

  1. process.env 的一切值都是字符串:"0"、"false"、"" 都是「有值」。判断开关要么 === 'true',要么统一走 envBool 这类转换工具。
  2. 优先级要清楚:真实的环境变量 > --env-file 注入的值。也就是说系统里已有 PORT 时,.env 里的 PORT 不会覆盖它(--env-file 不覆盖已存在变量),这让 CI 里可以「外面传值压过文件默认值」。
  3. NODE_ENV 是社区约定,不是语法:Node 本身不关心它的值,是框架和库约定 production/development/test 三档。node --env-file=.env NODE_ENV=production app.js(bash)或在 .env 里写 NODE_ENV=production 都行。
  4. .env 只进开发机,密钥只进部署平台:生产配置由部署系统(平台面板、CI/CD 变量、K8s Secret)注入,代码与 .env 都不背密钥。
  5. 配置模块是唯一出口:所有 process.env 的读取收敛到一个 config 文件(做类型转换、默认值、freeze),业务代码只 import config——换部署方式时只改一处。

你可能会懵的地方

  • 环境变量到底「活」在哪? 活在进程的内存里,从父进程(shell/部署系统)继承。PowerShell $env:X="1" 只影响当前会话及其子进程;换个终端就没有。永久生效要走系统设置或部署平台。

  • .env 和 .env.example 都要提交吗? .env 不提交(有真实密钥);.env.example 提交(只有字段名和占位值,是给协作者的说明书)。git 提交前用 git status 确认 .env 没混进来。

  • --env-file 报 ENOENT 怎么办? 文件不存在默认报错。CI/生产没有 .env 文件属正常,用 --env-file-if-missing=.env(Node 20.12+)或生产上不传该参数,让值走部署平台注入。

  • NODE_ENV 和自造的 APP_ENV 冲突吗? 不冲突但别双轨。社区生态(Express、很多库)认 NODE_ENV,统一用它;特殊需求(同一进程按租户切换)再造独立变量,但要在 config 模块里写清优先级。

  • 密钥不小心提交过 git 怎么办? 当作已泄露处理:立刻在服务方轮换密钥,然后才考虑清历史(git filter-repo)。改 .gitignore 只能防未来,撤不回已推送到远端的历史。

错法对照

错法问题正解
if (process.env.DEBUG) 当布尔用字符串 "false" 也是真值,开关失灵envBool 转换:v === 'true'
if (process.env.PORT === 3000)与字符串 "3000" 永不相等Number(process.env.PORT) === 3000
.env 提交到 git密钥永久进历史,等于公开.gitignore 忽略,仅提交 .env.example
业务代码里到处 process.env.XXX默认值、类型转换满天飞,改一处漏三处收敛到 config 模块,Object.freeze
生产密钥写进 .env 打包进镜像镜像被拉取即泄露生产值由部署平台运行时注入
--env-file 在无 .env 的 CI 里直接崩构建中断--env-file-if-missing 或按环境裁剪参数

卡点急救

  1. 设置了变量但读到 undefined:三查——注入和启动是否同进程(PowerShell 里 $env:X="1" 与 node 命令要挨着执行);变量名拼写;.env 文件里是否写了空格(PORT =3000 会解析出键 PORT 或值 3000,等号两边别加空格)。
  2. --env-file 报 ENOENT:路径不对(相对 cwd)或文件缺失;用相对项目根的路径,CI 场景换 --env-file-if-missing。
  3. PowerShell 一次传多个变量:$env:A="1"; $env:B="2"; node app.js——分号串接,或在同一行用 $env:A="1"; $env:B="2"; node app.js。
  4. 改了 .env 不生效:环境变量在进程启动时读取,运行中改文件无效;重启进程(node --watch 监听的是 js 文件,.env 变化需手动重启或升级 nodemon 配置)。
  5. JSON.stringify(process.env) 打印出密钥:调试时全量打印 env 会把密钥写进日志。只打印用到的 key,或做白名单过滤。

面试常问

  • 为什么要把配置放到环境变量? 12-Factor 的配置分离:同一份代码在 dev/test/prod 用不同值;密钥不进代码库和镜像,只进部署环境;换环境零代码改动。同时进程启动时注入,运行期只读,避免热改配置带来的状态混乱。

  • NODE_ENV 的作用是什么? 社区约定的运行环境标识(production/development/test),框架与库据此切换行为(Express 的视图缓存、错误详情泄露等)。它由人设置,Node 不强制校验;安全上注意「production 检测」别依赖字符串猜测,统一走 config。

  • .env 文件管理的最佳实践? .env 本地开发用且 git 忽略;.env.example 提交作为字段清单;生产由部署平台注入真实值;Node 20.6+ 用 --env-file 加载,或用 dotenv 兼容存量项目;启动时对必需变量做存在性校验,缺失立即报错退出。

  • process.env 的值都是什么类型?怎么安全取数字/布尔? 全是字符串或 undefined。安全取值要显式转换并给默认值:Number.parseInt + isNaN 校验、布尔判断 === 'true' || === '1',封装成 envInt/envBool 统一处理。

自测三问

  1. process.env.PORT 读到 "3000",process.env.PORT === 3000 的结果是什么?为什么?
  2. .env 和 .env.example 分别提交 git 吗?各自的用途是什么?
  3. node --env-file=.env app.js 时,系统里已存在的同名环境变量和 .env 里的值谁赢?

小结一句话

环境变量让「一份代码、多套配置」成立:值全是字符串要转换、.env 只进开发机、config 模块做唯一出口、密钥泄露过一次就当已泄密处理。

延伸

  • 12-Factor 配置篇
  • Node.js --env-file 文档
  • 下一课:Streams 基础——为什么 2GB 的文件不能用 readFile,流和背压才是大文件的正确打开方式。
加载本节练习…
← 上一课事件循环与异步下一课 →Streams 基础