Node.js 完整指南:后端开发教程 2026
通过本综合指南掌握 Node.js 后端开发。涵盖事件循环、Express.js、REST API、JWT 认证、文件系统、数据库集成、测试、PM2 和 Docker 部署,以及 Node.js vs Deno vs Bun 对比。
- Node.js 使用 Chrome V8 引擎在服务器端运行 JavaScript,采用非阻塞事件驱动的 I/O 模型。
- 使用 Express.js 进行路由和中间件处理;按 controllers、services、routes 文件夹组织项目。
- 使用 JWT + bcrypt 进行认证;将 token 存储在 httpOnly cookie 中;始终在保存前哈希密码。
- 在生产环境使用 PM2 进行进程管理;使用 Docker 进行容器化部署。
- 永远不要阻塞事件循环——对 CPU 密集型任务使用 async/await、流和 Worker 线程。
什么是 Node.js?
Node.js 是一个开源、跨平台的 JavaScript 运行时环境,能在浏览器之外执行 JavaScript 代码。它基于 Chrome 的 V8 JavaScript 引擎构建,由 Ryan Dahl 于 2009 年发布,使开发者能够将 JavaScript 用于服务器端编程,从而实现前后端统一使用同一种语言。
与每个请求创建新线程的传统服务器端语言(Apache/PHP 模型)不同,Node.js 使用单线程、事件驱动、非阻塞 I/O 模型。这种架构对于 I/O 密集型工作负载非常高效:Web 服务器、REST API、实时聊天、流媒体服务和微服务。
- Node.js 使用 V8 引擎和 libuv 实现非阻塞、事件驱动的 I/O。
- 事件循环是单线程的,但将 I/O 卸载到操作系统内核和线程池。
- npm 拥有超过 200 万个包——是软件开发中最大的生态系统。
- Express.js 是在 Node.js 中构建 HTTP 服务器和 API 的事实标准。
- JWT + bcrypt 是无状态 REST API 中最常见的认证模式。
- PM2 支持零停机部署、集群和进程监控。
- 2026 年 Node.js、Deno 和 Bun 都可用于生产;Node.js 在生态系统成熟度上胜出。
Node.js 架构:事件循环与非阻塞 I/O
在编写生产代码之前,理解 Node.js 的架构至关重要。最重要的概念是事件循环——这是使 Node.js 能够在不创建新操作系统线程的情况下处理数千个并发连接的机制。
单线程模型
Node.js JavaScript 代码运行在单线程上。这听起来像是一个限制,但对于 I/O 密集型应用来说实际上是一个优势。当 Node.js 需要读取文件或发起网络请求时,它将工作委托给操作系统(通过 libuv),然后继续处理其他任务。当操作系统完成后,它在事件循环中排队一个回调。
// 错误做法:阻塞事件循环(永远不要这样做)
const data = fs.readFileSync('/large-file.csv'); // 阻塞所有其他请求!
process(data);
// 正确做法:使用回调的非阻塞方式
fs.readFile('/large-file.csv', 'utf8', (err, data) => {
if (err) throw err;
process(data);
});
// 更好的做法:使用 async/await 的非阻塞方式
async function loadData() {
const data = await fs.promises.readFile('/large-file.csv', 'utf8');
process(data);
}事件循环阶段
事件循环每次循环处理六个阶段的回调。理解这些阶段有助于调试时序问题并编写可预测的异步代码:
setTimeout(() => console.log('1: setTimeout'), 0); // 定时器阶段
setImmediate(() => console.log('2: setImmediate')); // 检查阶段
process.nextTick(() => console.log('3: nextTick')); // 微任务(下一阶段前)
Promise.resolve().then(() => console.log('4: Promise')); // 微任务(下一阶段前)
console.log('5: 同步代码');
// 输出顺序:
// 5: 同步代码
// 3: nextTick (微任务在下一个事件循环阶段前运行)
// 4: Promise (微任务在下一个事件循环阶段前运行)
// 1: setTimeout (定时器阶段)
// 2: setImmediate (检查阶段)npm 与 package.json:依赖管理
{
"name": "my-api-server",
"version": "1.2.3",
"description": "生产就绪的 REST API",
"main": "src/index.js",
"engines": { "node": ">=20.0.0" },
"scripts": {
"start": "node src/index.js",
"dev": "nodemon src/index.js",
"test": "jest --coverage",
"lint": "eslint src/"
},
"dependencies": {
"express": "^4.18.2",
"jsonwebtoken": "^9.0.2",
"bcrypt": "^5.1.1",
"dotenv": "^16.3.1"
},
"devDependencies": {
"jest": "^29.7.0",
"nodemon": "^3.0.2",
"supertest": "^6.3.4"
}
}Express.js:构建 HTTP 服务器和 API
const express = require('express');
const app = express();
app.use(express.json());
// 基本路由
app.get('/api/health', (req, res) => {
res.json({ status: 'ok', uptime: process.uptime() });
});
// 带参数的路由
app.get('/api/users/:id', async (req, res, next) => {
try {
const user = await User.findById(req.params.id);
if (!user) return res.status(404).json({ error: '用户未找到' });
res.json(user);
} catch (err) {
next(err);
}
});
// 错误处理中间件(必须有 4 个参数)
app.use((err, req, res, next) => {
const status = err.status || 500;
res.status(status).json({ error: err.message || '服务器内部错误' });
});
app.listen(3000, () => console.log('Express 运行在端口 3000'));环境变量与配置
# .env(永远不要提交此文件!)
NODE_ENV=development
PORT=3000
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
JWT_SECRET=your-super-secret-key
# .gitignore
.env
.env.local
.env.production// config/env.js — 启动时验证所有必要变量
require('dotenv').config();
function required(name) {
const value = process.env[name];
if (!value) throw new Error(`缺少必要的环境变量:${name}`);
return value;
}
module.exports = {
NODE_ENV: process.env.NODE_ENV || 'development',
PORT: parseInt(process.env.PORT || '3000', 10),
DATABASE_URL: required('DATABASE_URL'),
JWT_SECRET: required('JWT_SECRET'),
};构建 REST API:CRUD 操作
// 完整的 CRUD REST API 示例
router.get('/api/posts', async (req, res, next) => {
try {
const { page = 1, limit = 20 } = req.query;
const [posts, total] = await Promise.all([
Post.findMany({ limit: Number(limit), offset: (Number(page) - 1) * Number(limit) }),
Post.count(),
]);
res.json({ data: posts, meta: { page: Number(page), total } });
} catch (err) {
next(err);
}
});
router.post('/api/posts', authenticate, async (req, res, next) => {
try {
const post = await Post.create({ ...req.body, authorId: req.user.id });
res.status(201).json(post); // 201 Created
} catch (err) {
next(err);
}
});
router.delete('/api/posts/:id', authenticate, async (req, res, next) => {
try {
await Post.delete(req.params.id);
res.status(204).send(); // 204 No Content
} catch (err) {
next(err);
}
});认证:JWT 与 bcrypt
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
// 注册时哈希密码
const hashedPassword = await bcrypt.hash(plainText, 12); // 12轮,约300ms
// 登录时验证密码
const isValid = await bcrypt.compare(plainText, hashedPassword);
// 生成 JWT
const token = jwt.sign(
{ sub: userId, type: 'access' },
process.env.JWT_SECRET,
{ expiresIn: '15m' }
);
// 认证中间件
function authenticate(req, res, next) {
const token = req.headers.authorization?.slice(7);
if (!token) return res.status(401).json({ error: '未认证' });
try {
const payload = jwt.verify(token, process.env.JWT_SECRET);
req.user = { id: payload.sub };
next();
} catch {
res.status(401).json({ error: 'token 无效或已过期' });
}
}Node.js vs Deno vs Bun 对比
| 特性 | Node.js 22 | Deno 2.x | Bun 1.x |
|---|---|---|---|
| 引擎 | V8 | V8 | JavaScriptCore |
| TypeScript 支持 | 原生(实验性) | 原生一等公民 | 原生一等公民 |
| 包管理器 | npm/pnpm/yarn | deno add | 内置 bun install |
| npm 兼容性 | 100% | ~95% | ~98% |
| 安全模型 | 无限制 | 需要权限声明 | 无限制 |
| HTTP 性能 | 基准 | 约快 2 倍 | 约快 4 倍 |
| LTS 支持 | 有(Active LTS) | 无正式 LTS | 无正式 LTS |
| 最适合 | 生产、企业级 | TypeScript 优先团队 | 追求速度的新项目 |
部署:PM2 与 Docker
// ecosystem.config.js — PM2 配置
module.exports = {
apps: [{
name: 'api-server',
script: 'src/index.js',
instances: 'max', // 使用所有 CPU 核心
exec_mode: 'cluster', // 集群模式
max_memory_restart: '500M',
env_production: {
NODE_ENV: 'production',
PORT: 3000,
},
}],
};# PM2 常用命令
pm2 start ecosystem.config.js --env production
pm2 status # 查看所有进程
pm2 logs api-server # 查看日志
pm2 reload api-server # 零停机重载
pm2 startup && pm2 save # 开机自启常见问题
Node.js 是什么,用于什么?
Node.js 是基于 Chrome V8 引擎的跨平台 JavaScript 运行时。主要用于构建后端 Web 服务器、REST API、实时应用(聊天、游戏)、CLI 工具和微服务。其非阻塞 I/O 模型使其非常适合高并发 I/O 密集型工作负载。
Node.js 事件循环如何工作?
事件循环让 Node.js 在单线程的情况下实现非阻塞。它将 I/O 操作卸载到操作系统内核,完成后处理其回调。循环有六个阶段:定时器、挂起回调、空闲/准备、轮询、检查(setImmediate)和关闭回调。Promise 和 process.nextTick 等微任务在每个阶段之间运行。
如何在 Node.js 中处理 async/await 错误?
用 try/catch 块包裹 async 操作。在 Express.js 中,始终在异步路由处理器中调用 next(err) 将错误传递到错误处理中间件。使用 process.on('unhandledRejection') 作为捕获遗漏 rejection 的最后手段。
如何在 Node.js 中管理环境变量?
使用 dotenv 在开发时加载 .env 文件。永远不要将 .env 提交到版本控制。提交一个带有空值的 .env.example 作为文档。在启动时验证必要的变量,以便在任何变量缺失时快速失败。在生产环境中,通过托管平台设置环境变量。
如何实现 JWT 认证?
安装 jsonwebtoken 和 bcrypt。用 bcrypt.hash() 哈希密码后再存储。登录时用 bcrypt.compare() 验证,然后用 jwt.sign() 签发 JWT。用中间件调用 jwt.verify() 保护路由。访问 token 设置短有效期(15m),使用 httpOnly cookie 中的刷新 token 来提升安全性。
新项目应该选择 Node.js、Deno 还是 Bun?
Node.js 因其生态系统和 LTS 支持是生产环境最安全的选择。Bun 非常适合需要速度和出色开发体验的新项目。Deno 适合重视默认安全性和 Web API 兼容性的 TypeScript 优先团队。2026 年三者都可以用于生产。
如何将 Node.js 应用部署到生产环境?
在 VPS 上使用 PM2 进行进程管理,支持集群和自动重启。使用多阶段 Dockerfile 进行容器化部署,通过 Docker Compose 或 Kubernetes 部署。设置 NODE_ENV=production,在 Node.js 前使用 Nginx 作为反向代理,通过 Let's Encrypt 启用 HTTPS,并使用 PM2 内置仪表板或专用 APM 工具进行监控。
如何避免 Node.js 中的内存泄漏?
常见原因和解决方法:(1)未移除的事件监听器——组件销毁时调用 emitter.removeListener();(2)无淘汰策略的缓存——使用带 TTL 的 LRU 缓存;(3)闭包持有大对象引用——让变量超出作用域;(4)未关闭的数据库连接——始终在 finally 块中释放连接。使用 Chrome DevTools 的内存分析器来查找泄漏。