Node.js 部署与日志
代码写完只算完成一半,让它在服务器上稳定跑起来、出了问题能查日志,才是完整的交付。这篇讲部署的完整流程,用 PM2 保持进程常驻,用 Winston 记录日志,最后加一个健康检查接口。
部署前的准备
生产环境安装依赖
服务器上安装依赖要用 npm ci,它按 package-lock.json 里的精确版本安装,速度快且结果可复现,不会因为依赖升级引入意外。npm 安装的细节回顾 Node.js npm 入门。
npm ci --omit=dev
--omit=dev 跳过开发依赖,生产环境只需要 express、prisma 这些运行时包。
环境变量
数据库地址、JWT 密钥这些配置在服务器上要用生产值,.env 文件不要提交到 git,部署时在服务器上手动创建。多环境配置的做法见 Node.js 环境变量与配置。
# 服务器上的 .env
PORT=3000
DATABASE_URL="mysql://user:密码@localhost:3306/blog"
JWT_SECRET="换成一段足够长的随机字符串"
NODE_ENV=production
编译数据库
生产库要执行迁移建表
npx prisma migrate deploy
PM2 进程管理
直接 node app.js 启动的进程有两个问题,终端一关进程就死,代码一抛未捕获异常进程直接退出。PM2 是 Node.js 最常用的进程管理器,进程常驻、崩溃自动重启、日志统一管理。
# 全局安装
npm install -g pm2
# 启动应用
pm2 start src/server.js --name blog-api
# 查看进程状态
pm2 status
# 实时查看日志
pm2 logs blog-api
# 重启 / 停止
pm2 restart blog-api
pm2 stop blog-api
服务器重启后要能自动拉起进程,执行一次保存和开机自启配置
pm2 save
pm2 startup
pm2 startup 会输出一条需要 root 权限执行的命令,复制执行一次即可。
ecosystem 配置文件
项目里放一份 ecosystem.config.js,把启动参数固化下来,团队其他人部署时不用猜
// ecosystem.config.js
module.exports = {
apps: [
{
name: 'blog-api',
script: 'src/server.js',
instances: 1,
autorestart: true,
max_memory_restart: '300M',
env: {
NODE_ENV: 'production'
}
}
]
}
启动时用 pm2 start ecosystem.config.js 即可。
Winston 日志
日志的目的是出问题时能还原现场。Winston 是 Node.js 最流行的日志库,支持分级、多种输出、按天切分文件。
npm install winston
// src/utils/logger.js
const winston = require('winston')
const logger = winston.createLogger({
level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.printf(({ timestamp, level, message }) => {
return `${timestamp} [${level}] ${message}`
})
),
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'logs/app.log' })
]
})
module.exports = logger
开发环境日志级别是 debug,方便调试,生产环境是 info,减少噪音。日志级别从低到高是 debug、info、warn、error,级别越高输出越少。代码里这样用
const logger = require('../utils/logger')
logger.info('用户登录成功', userId)
logger.error('数据库连接失败', err.message)
访问日志用 morgan 记录每个请求的方法、路径、状态码和耗时,先装包
npm install morgan
const morgan = require('morgan')
const logger = require('./utils/logger')
app.use(morgan('combined', {
stream: { write: (msg) => logger.info(msg.trim()) }
}))
morgan 的用法回顾 Node.js Express 中间件,这里把输出流接到 Winston,访问日志和业务日志统一落盘。
健康检查接口
运维监控、负载均衡都要一个探活接口。返回 200 说明进程活着,再顺手查一下数据库连通性,避免”进程活着但数据库挂了”的假健康。
// src/controllers/health.controller.js
const { PrismaClient } = require('@prisma/client')
const prisma = new PrismaClient()
async function health(req, res) {
try {
await prisma.$queryRaw`SELECT 1`
res.json({ status: 'ok', db: 'ok', time: new Date().toISOString() })
} catch (err) {
res.status(503).json({ status: 'error', db: 'down' })
}
}
module.exports = { health }
// app.js 里挂载
app.get('/health', healthController.health)
Nginx 反向代理
Node 应用默认监听 3000 端口,用户直接访问 3000 端口体验不好也不安全。标准做法是 Nginx 监听 80 端口,把请求转发给 Node,顺带代理静态资源和启用 HTTPS。
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
HTTPS 证书用 Let’s Encrypt 免费申请,配置好后全站强制跳转 https,安全相关的要求回顾 Node.js Web 安全。
完整部署流程
把博客 API 部署到一台 Ubuntu 云服务器,完整命令序列
# 1. 安装 Node 20 LTS(版本管理用 nvm)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
nvm install 20
# 2. 拉取代码
git clone https://你的仓库地址/blog-api.git
cd blog-api
# 3. 安装生产依赖并建表
npm ci --omit=dev
npx prisma migrate deploy
# 4. 创建 .env 并填入生产配置
vi .env
# 5. PM2 启动并设置开机自启
npm install -g pm2
pm2 start ecosystem.config.js
pm2 save
pm2 startup
# 6. 验证
curl http://localhost:3000/health
常见坑
| 问题 | 原因 | 解决 |
|---|---|---|
| 端口被占用 | 上次进程没停干净 | pm2 stop 后检查 lsof -i:3000 |
| .env 没生效 | 文件没传到服务器 | 确认 .env 在项目根目录且被 .gitignore 忽略 |
| 数据库连不上 | 密码或地址不对 | 检查 DATABASE_URL,确认 MySQL 已启动 |
| 日志文件无限增长 | 没有轮转策略 | 配置 maxsize 和 maxFiles 限制文件大小 |
| 代码更新不生效 | 改了代码没重启 | pm2 restart blog-api |
学习路线回顾
到这里整个 Node.js 教程就结束了,25 篇从 Node.js 是什么 的环境搭建、核心模块、异步编程、事件循环,到 Express 框架、数据库、认证安全、实战部署,已经形成一条完整的后端开发路线。接着可以按兴趣往这几个方向深入,Stream 处理大文件读写、Docker 容器化部署、TypeScript 给项目加类型、微服务拆分架构,每一条都够再学一套课程。