首页 / Node.js 教程 / 开发与生产环境差异

Node.js 教程

开发与生产环境差异

本教程共 76 篇 · 第 66 篇 · 更新于 2026-07-25 · 约 7 分钟阅读

Node.js环境变量生产环境配置NODE_ENV

66. 开发与生产环境差异

本节目标:环境变量、配置管理和生产环境注意事项。

开发环境的代码能跑,不代表生产环境也能跑。我见过太多 “本地好好的,上线就崩” 的事故,根因往往是对环境差异缺乏认知。这一章我们把开发和生产的鸿沟填平。

NODE_ENV:一个被滥用的变量

NODE_ENV 是 Node.js 生态里最著名的环境变量,但它的用法一直存在争议。官方文档的态度很明确:

Node.js 本身不区分开发和生产环境。NODE_ENV 只是 npm 生态里某些库的约定。

很多教程教你这么写:

if (process.env.NODE_ENV === 'development') {
  // 开发环境配置
}
if (process.env.NODE_ENV === 'production') {
  // 生产环境配置
}

这看起来无害,实际上埋了雷:测试环境和 staging 环境走哪条分支?如果漏了判断,可能在测试通过但上线后行为不一致。官方甚至把 “给 NODE_ENV 设成非 production 的值” 视为一种反模式。

更稳妥的做法是:默认按生产环境配置,开发环境用特定开关覆盖

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

const config = {
  port: process.env.PORT || 3000,
  logLevel: process.env.LOG_LEVEL || 'info',
  db: {
    host: process.env.DB_HOST,
    poolSize: isDev ? 2 : 20,
  },
  // 生产默认值,开发按需覆盖
};

这样即使 NODE_ENV 没设或者设了奇怪的值,服务也能以安全的生产默认值启动。

配置管理:代码里不要写死配置

数据库地址、API 密钥、缓存地址,这些都不该出现在代码仓库里。推荐的做法是环境变量 + 配置文件 + 默认值三层结构:

import { config } from 'dotenv';
config(); // 加载 .env 文件(开发环境用)

const cfg = {
  port: parseInt(process.env.PORT, 10) || 3000,
  db: {
    host: process.env.DB_HOST || 'localhost',
    port: parseInt(process.env.DB_PORT, 10) || 5432,
    name: process.env.DB_NAME || 'myapp',
    user: process.env.DB_USER,
    password: process.env.DB_PASSWORD,
  },
  redis: {
    url: process.env.REDIS_URL,
  },
  jwtSecret: process.env.JWT_SECRET,
};

// 启动时校验必填项
function validate() {
  const required = ['DB_USER', 'DB_PASSWORD', 'JWT_SECRET'];
  for (const key of required) {
    if (!process.env[key]) {
      throw new Error(`缺少必填环境变量: ${key}`);
    }
  }
}
validate();

export default cfg;

.env 文件只存在于开发环境,提交到 .gitignore 里。生产环境的变量由 CI/CD 或 K8s Secret 注入。

Warning

永远不要把 .env 文件提交到 Git。我见过有项目把生产数据库密码推到公开仓库,结果被爬虫扫到,数据全泄露。如果不小心提交了,立刻轮换密钥,不要只删文件了事。

错误处理:开发要详细,生产要安全

开发环境看到完整的错误堆栈,能快速定位 bug。生产环境暴露堆栈,等于把代码结构送给攻击者。

import express from 'express';
const app = express();

app.use((err, req, res, next) => {
  // 1. 永远先记录完整错误
  req.log.error({ err }, '请求处理异常');

  // 2. 根据环境决定返回什么
  if (process.env.NODE_ENV === 'development') {
    res.status(500).json({
      error: err.message,
      stack: err.stack,
    });
  } else {
    res.status(500).json({
      error: 'Internal Server Error',
      requestId: req.id, // 给用户一个 ID,方便客服根据日志定位
    });
  }
});

给用户一个 requestId,而不是技术细节。运维人员拿到这个 ID 去日志系统里一查,就能找到完整的堆栈和上下文。

优雅退出:不要突然掐断

容器编排平台(Kubernetes、Docker Swarm)在重启或缩容时,会先给进程发 SIGTERM 信号,等一段宽限期(默认 30 秒)后再发 SIGKILL。你的应用需要在这段时间里:

  1. 停止接收新请求(关闭 HTTP server)。
  2. 等现有请求处理完(或超时)。
  3. 关闭数据库连接、释放其他资源。
  4. 退出进程。
import http from 'node:http';

const server = http.createServer((req, res) => {
  res.end('ok');
});

server.listen(3000, () => console.log('服务启动'));

let isShuttingDown = false;

function gracefulShutdown(signal) {
  console.log(`收到 ${signal},开始优雅关闭`);
  isShuttingDown = true;

  // 停止接收新连接
  server.close(() => {
    console.log('HTTP server 已关闭');
    // 关闭数据库连接等...
    process.exit(0);
  });

  // 兜底:10 秒后强制退出
  setTimeout(() => {
    console.error('优雅关闭超时,强制退出');
    process.exit(1);
  }, 10000);
}

process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
Tip

如果你用了 cluster,主进程收到 SIGTERM 后要先通知所有 worker 关闭,等全部退出后再退出主进程。否则容器平台可能认为主进程已退出,但 worker 还在跑,导致连接被粗暴切断。

进程管理:开发用 nodemon,生产用 PM2/systemd

开发阶段文件一改就自动重启,用 nodemon 或 Node.js 内置的 --watch(v18+):

{
  "scripts": {
    "dev": "node --watch server.mjs"
  }
}

生产环境不能靠 --watch,需要专业的进程管理:

  • PM2:Node.js 生态最成熟的进程管理器,支持 cluster 模式、日志收集、自动重启、零停机部署。
  • systemd:Linux 系统级方案,适合把 Node.js 服务当作系统守护进程运行。
  • Docker + K8s:容器化方案,由编排平台负责进程生命周期。

PM2 配置示例:

// ecosystem.config.cjs
module.exports = {
  apps: [{
    name: 'api-server',
    script: './server.mjs',
    instances: 'max',      // 用满所有 CPU 核
    exec_mode: 'cluster',  // cluster 模式
    env: {
      NODE_ENV: 'development',
      PORT: 3000,
    },
    env_production: {
      NODE_ENV: 'production',
      PORT: 8080,
    },
    log_file: './logs/combined.log',
    out_file: './logs/out.log',
    error_file: './logs/error.log',
    log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
    max_memory_restart: '500M', // 内存超 500MB 自动重启
  }],
};

启动:pm2 start ecosystem.config.cjs --env production

开发 vs 生产差异速查表

维度开发环境生产环境
日志级别debug/traceinfo/warn
日志格式人类可读,带颜色结构化 JSON
错误响应完整堆栈通用错误信息 + requestId
代码监听nodemon / node --watchPM2 / systemd / K8s
进程数1CPU 核数(cluster)
数据库连接池小(2~5)大(20~50)
静态资源本地文件CDN + 缓存头
HTTPS本地自签名证书正式证书(Let’s Encrypt/商业证书)
调试端口--inspect 开着关闭或限制 IP
环境变量来源.env 文件K8s Secret / 云厂商参数 store

12-Factor App 原则

Heroku 提出的 12-Factor App 方法论,对 Node.js 生产化实践非常有指导意义。和我们的主题最相关的几条:

  1. 配置存储在环境中:代码和配置严格分离,同一套代码可以在不同环境运行。
  2. 把后端服务当作附加资源:数据库、缓存、消息队列都是资源,通过 URL 配置,不假设本地存在。
  3. 进程无状态、可共享:进程内不保存 session、上传文件等,所有状态放外部存储。
  4. 通过进程模型扩展:用进程数扩展并发能力,而不是在进程里加线程。
  5. 快速启动、优雅终止:启动要快,方便扩容;退出要优雅,不丢请求。