目录
- Node.js 运行时核心机制
- 模块系统:ESM vs CJS
- 异步编程与 async/await
- 主流 Web 框架深度对比
- 中间件模式解析
- ORM 与数据库:Prisma vs TypeORM
- 包管理器:npm / yarn / pnpm
- TypeScript 在 Node.js 中的应用
- 生产部署:PM2 与 Docker
- 总结与选型建议
1. Node.js 运行时核心机制
1.1 事件循环(Event Loop)
Node.js 是单线程但能处理高并发的关键,就在于其 事件循环(Event Loop) 机制。事件循环是 Node.js 运行时的核心,它让 JavaScript 在单线程中通过异步回调实现非阻塞 I/O。
事件循环的六个阶段(Phase):
┌───────────────────────────┐
┌─>│ timers │ ← setTimeout / setInterval 回调
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ pending callbacks │ ← 延迟到下一轮的 I/O 回调
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ idle, prepare │ ← 内部使用
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ poll │ ← 轮询 I/O 事件(核心阶段)
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ check │ ← setImmediate 回调
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ close callbacks │ ← close 事件回调
│ └───────────────────────────┘- timers:执行
setTimeout和setInterval的回调。 - pending callbacks:执行延迟到下一轮循环的 I/O 回调(如 TCP 错误)。
- poll:最重要的阶段。获取新的 I/O 事件,执行与 I/O 相关的回调。如果 poll 队列为空,会检查是否有
setImmediate回调或定时器到期。 - check:执行
setImmediate回调。 - close callbacks:执行关闭事件的回调(如
socket.on('close'))。
微任务(Microtask) 在每个阶段之间执行,包括 process.nextTick 和 Promise 回调。其中 process.nextTick 优先级高于 Promise 回调。
// 理解执行顺序
console.log('1: 同步代码');
setTimeout(() => console.log('2: setTimeout'), 0);
setImmediate(() => console.log('3: setImmediate'));
Promise.resolve().then(() => console.log('4: Promise.then'));
process.nextTick(() => console.log('5: process.nextTick'));
console.log('6: 同步代码结束');
// 输出顺序:
// 1: 同步代码
// 6: 同步代码结束
// 5: process.nextTick ← 微任务,优先级最高
// 4: Promise.then ← 微任务,次高
// 2: setTimeout ← timers 阶段 或 3: setImmediate
// 3: setImmediate ← check 阶段 或 2: setTimeout1.2 非阻塞 I/O(Non-blocking I/O)
Node.js 的 I/O 操作(文件读取、网络请求、数据库查询)默认是异步非阻塞的。当发起 I/O 操作时,Node.js 不会等待结果返回,而是立即继续执行后续代码。I/O 操作完成后,回调函数被放入事件循环的相应队列中。
const fs = require('node:fs');
// 非阻塞(异步)
console.time('async');
fs.readFile('large-file.txt', 'utf8', (err, data) => {
console.timeEnd('async'); // 回调中计时
});
console.log('文件读取已发起,继续执行...');
// 阻塞(同步)—— 不推荐在服务器中使用
console.time('sync');
const data = fs.readFileSync('large-file.txt', 'utf8');
console.timeEnd('sync');libuv 是 Node.js 底层的 C 库,负责处理事件循环和线程池。对于文件系统操作和 DNS 查询,libuv 使用内部的线程池(默认 4 个线程),而对于网络 I/O,则使用操作系统原生的异步接口(如 epoll / kqueue / IOCP)。
2. 模块系统:ESM vs CJS
Node.js 支持两种模块系统:CommonJS(CJS) 和 ES Modules(ESM)。
2.1 CommonJS(CJS)
Node.js 的默认模块系统,使用 require() 和 module.exports。
// 📁 math.cjs
function add(a, b) { return a + b; }
function multiply(a, b) { return a * b; }
module.exports = { add, multiply };
exports.PI = 3.14159; // 另一种导出方式
// 📁 app.cjs
const math = require('./math.cjs');
console.log(math.add(2, 3)); // 5
console.log(math.PI); // 3.14159CJS 特点:
- 同步加载:
require()是同步的,适合服务端。 - 运行时解析:模块在运行时加载,支持动态路径(如
require(./lang/${lang}.js))。 - 值拷贝:导出的是值的拷贝(基本类型)或引用(对象)。
- 缓存:模块首次
require后会被缓存,后续require返回同一实例。
2.2 ES Modules(ESM)
JavaScript 的官方模块标准,使用 import / export 语法。
// 📁 math.mjs
export function add(a, b) { return a + b; }
export const PI = 3.14159;
export default function multiply(a, b) { return a * b; }
// 📁 app.mjs
import multiply, { add, PI } from './math.mjs';
console.log(add(2, 3)); // 5
console.log(PI); // 3.14159
console.log(multiply(2, 3)); // 6启用 ESM 的方式:
- 文件使用
.mjs扩展名。 - 在
package.json中设置"type": "module"。
{
"name": "my-app",
"type": "module", // 所有 .js 文件按 ESM 解析
"exports": {
"./utils": "./src/utils.js"
}
}ESM 特点:
- 异步加载:支持顶层
await,适合浏览器和服务端。 - 静态解析:
import在模块解析阶段处理,支持 Tree Shaking。 - 值引用:导出的是活的绑定(live binding),导入模块能看到导出模块的变量变化。
- 严格模式:ESM 默认启用严格模式。
2.3 CJS 与 ESM 互操作
// CJS 中导入 ESM(需要使用动态 import)
// 📁 wrapper.cjs
(async () => {
const esmModule = await import('./esm-module.mjs');
console.log(esmModule.default);
})();
// ESM 中导入 CJS
// 📁 app.mjs
import cjsModule from './cjs-module.cjs'; // default 导入
import { namedExport } from './cjs-module.cjs'; // 具名导入建议:新项目统一使用 ESM。如果维护旧项目,了解两者的差异和互操作方式非常必要。
3. 异步编程与 async/await
3.1 回调地狱 → Promise → async/await
Node.js 的异步演进史:
// ❌ 回调地狱(Callback Hell)
getUser(id, (err, user) => {
if (err) return handleError(err);
getOrders(user.id, (err, orders) => {
if (err) return handleError(err);
getProducts(orders[0].id, (err, products) => {
if (err) return handleError(err);
console.log(products);
});
});
});
// ✅ Promise 链式调用
getUser(id)
.then(user => getOrders(user.id))
.then(orders => getProducts(orders[0].id))
.then(products => console.log(products))
.catch(handleError);
// ✅ async/await(最推荐)
async function loadUserProducts(id) {
try {
const user = await getUser(id);
const orders = await getOrders(user.id);
const products = await getProducts(orders[0].id);
console.log(products);
} catch (err) {
handleError(err);
}
}3.2 并行执行
当多个异步操作互不依赖时,应使用 Promise.all 并行执行:
// ❌ 串行等待(浪费时间)
const user = await fetchUser(id);
const posts = await fetchPosts(id); // 等到 user 返回后才开始
// ✅ 并行执行
const [user, posts] = await Promise.all([
fetchUser(id),
fetchPosts(id),
]);
// Promise.allSettled —— 不抛出异常,返回所有结果
const results = await Promise.allSettled([
fetchUser(id),
fetchPosts(id),
]);
results.forEach(r => {
if (r.status === 'fulfilled') console.log(r.value);
if (r.status === 'rejected') console.error(r.reason);
});3.3 错误处理最佳实践
// 在 Express 中使用 async 中间件
const asyncHandler = (fn) => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
// 使用方式
app.get('/users/:id', asyncHandler(async (req, res) => {
const user = await db.findUser(req.params.id);
if (!user) {
return res.status(404).json({ error: '用户未找到' });
}
res.json(user);
}));4. 主流 Web 框架深度对比
Node.js 生态中有四大主流 Web 框架:Express、Koa、NestJS 和 Fastify。下面从多个维度进行对比。
| 特性 | Express | Koa | NestJS | Fastify |
|---|---|---|---|---|
| 首次发布 | 2010 | 2013 | 2017 | 2016 |
| 作者 | TJ Holowaychuk | TJ Holowaychuk | Kamil Myśliwiec | Matteo Collina |
| TypeScript 支持 | 社区 @types | 社区 @types | 一等公民 | 内置支持 |
| 中间件模型 | 线性(回调) | 洋葱模型(Koa) | 类似 Angular | 线性 + 插件 |
| 内置 DI | ❌ | ❌ | ✅ 依赖注入 | ❌ |
| 性能 | 中等 | 较好 | 中等 | 极高 |
| 插件生态 | 极丰富 | 较丰富 | 丰富(模块) | 丰富(插件) |
| 学习曲线 | 低 | 低 | 中高 | 中低 |
| 适用场景 | 传统 Web / REST API | 中间件密集型应用 | 大型企业应用 | 高性能微服务 |
4.1 Express —— 经典之选
Express 是 Node.js 最老牌、使用最广泛的 Web 框架,生态系统极为成熟。
// 📁 express-app.js
import express from 'express';
const app = express();
const PORT = 3000;
// 内置中间件
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// 路由定义
app.get('/api/users', (req, res) => {
res.json([{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]);
});
app.get('/api/users/:id', (req, res) => {
const { id } = req.params;
res.json({ id: Number(id), name: `User ${id}` });
});
app.post('/api/users', (req, res) => {
const { name } = req.body;
res.status(201).json({ id: Date.now(), name });
});
// 中间件链示例(多个中间件处理同一个路由)
const validateUser = (req, res, next) => {
const { name } = req.body;
if (!name || name.trim().length < 2) {
return res.status(400).json({ error: '用户名至少2个字符' });
}
next(); // 传递给下一个中间件
};
const logRequest = (req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
next();
};
app.post('/api/users/validate',
logRequest, // 中间件 1:日志
validateUser, // 中间件 2:验证
(req, res) => { // 中间件 3:实际处理器
res.json({ message: '验证通过', body: req.body });
}
);
// 全局错误处理
app.use((err, req, res, next) => {
console.error('Unhandled Error:', err);
res.status(500).json({
error: '内部服务器错误',
...(process.env.NODE_ENV === 'development' && { detail: err.message }),
});
});
app.listen(PORT, () => {
console.log(`Express 服务已启动: http://localhost:${PORT}`);
});Express 优缺点:
- ✅ 学习门槛低,文档丰富,社区庞大。
- ✅ 中间件生态极为丰富(passport、multer、cors、helmet 等)。
- ❌ 中间件模型是线性回调,复杂链路的调试较为困难。
- ❌ 没有内置的 TypeScript 支持,需要额外配置。
- ❌ 对于大型应用,代码组织容易变得混乱。
4.2 Koa —— 优雅的中间件模型
Koa 由 Express 原班人马打造,利用 async/await 实现了优雅的洋葱模型中间件。
// 📁 koa-app.js
import Koa from 'koa';
import Router from '@koa/router';
import bodyParser from 'koa-bodyparser';
const app = new Koa();
const router = new Router();
// 日志中间件 —— 演示洋葱模型
app.use(async (ctx, next) => {
const start = Date.now();
console.log(`⏳ 请求进入: ${ctx.method} ${ctx.url}`);
await next(); // ⭐ 等待下游中间件执行
const ms = Date.now() - start;
console.log(`✅ 响应完成: ${ctx.method} ${ctx.url} — ${ms}ms`);
ctx.set('X-Response-Time', `${ms}ms`);
});
// body 解析
app.use(bodyParser());
// 错误处理中间件
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
ctx.body = {
error: err.message || '内部服务器错误',
};
ctx.app.emit('error', err, ctx);
}
});
// 路由
router.get('/api/users', (ctx) => {
ctx.body = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];
});
router.get('/api/users/:id', (ctx) => {
const { id } = ctx.params;
ctx.body = { id: Number(id), name: `User ${id}` };
});
router.post('/api/users', (ctx) => {
const { name } = ctx.request.body;
ctx.status = 201;
ctx.body = { id: Date.now(), name };
});
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(3000, () => {
console.log('Koa 服务已启动: http://localhost:3000');
});洋葱模型执行流程:
请求进入
│
▼
┌─ 中间件 1(前置逻辑)─┐
│ │ │
│ ▼ │
│ ┌─ 中间件 2(前置)─┐│
│ │ │ ││
│ │ ▼ ││
│ │ ┌─ 路由处理 ─┐ ││
│ │ └────────────┘ ││
│ │ │ ││
│ │ 中间件 2(后置)←─┘│
│ └───────────────────┘│
│ │ │
│ 中间件 1(后置)←──────┘
└───────────────────────┘
│
▼
响应返回Koa 优缺点:
- ✅ 洋葱模型让请求前后的逻辑清晰易控(日志、鉴权、计时)。
- ✅ 原生支持 async/await,代码更简洁现代。
- ❌ 内置功能极少,路由、body 解析等都需要额外安装中间件。
- ❌ 社区生态远不如 Express 丰富。
- ❌ 不适合需要严格架构的大型项目。
4.3 NestJS —— 企业级架构
NestJS 是目前最流行的 TypeScript 后端框架,采用模块化架构和依赖注入,受 Angular 启发。
// 📁 nest-app/src/users/users.controller.ts
import {
Controller,
Get,
Post,
Param,
Body,
HttpCode,
HttpStatus,
ParseIntPipe,
} from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
@Controller('api/users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get()
findAll() {
return this.usersService.findAll();
}
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.usersService.findOne(id);
}
@Post()
@HttpCode(HttpStatus.CREATED)
create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
}// 📁 nest-app/src/users/users.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
@Injectable()
export class UsersService {
private users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
];
findAll() {
return this.users;
}
findOne(id: number) {
const user = this.users.find(u => u.id === id);
if (!user) {
throw new NotFoundException(`用户 #${id} 未找到`);
}
return user;
}
create(dto: { name: string }) {
const newUser = { id: Date.now(), name: dto.name };
this.users.push(newUser);
return newUser;
}
}// 📁 nest-app/src/users/dto/create-user.dto.ts
import { IsString, MinLength } from 'class-validator';
export class CreateUserDto {
@IsString()
@MinLength(2, { message: '用户名至少2个字符' })
name: string;
}// 📁 nest-app/src/users/users.module.ts
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
@Module({
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}NestJS 核心概念:
| 概念 | 说明 | 装饰器示例 |
|---|---|---|
| 模块 (Module) | 组织代码的基本单元 | @Module({ imports, controllers, providers }) |
| 控制器 (Controller) | 处理 HTTP 请求 | @Controller()、@Get()、@Post() |
| 提供者 (Provider) | 业务逻辑(Service),可注入 | @Injectable() |
| DTO | 数据传输对象,验证请求体 | class-validator 装饰器 |
| 守卫 (Guard) | 鉴权 / 权限控制 | @Injectable() implements CanActivate |
| 拦截器 (Interceptor) | 请求/响应转换 | @Injectable() implements NestInterceptor |
| 管道 (Pipe) | 输入验证与转换 | ParseIntPipe、ValidationPipe |
| 过滤器 (Filter) | 异常处理 | @Catch() |
// 📁 全局验证管道示例
// main.ts
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true, // 去除 DTO 中未定义的属性
forbidNonWhitelisted: true, // 存在未定义属性时抛出异常
transform: true, // 自动转换类型(如 string → number)
}),
);
await app.listen(3000);
console.log('NestJS 服务已启动: http://localhost:3000');
}
bootstrap();NestJS 优缺点:
- ✅ 高度工程化,代码结构清晰,适合中大型团队。
- ✅ 内置 DI、模块化、守卫、拦截器、管道等企业级特性。
- ✅ TypeScript 一等公民,类型安全贯穿始终。
- ✅ 支持多种传输层(HTTP、WebSocket、gRPC、GraphQL、MQ)。
- ❌ 学习曲线较陡,需要理解装饰器、DI、模块系统等概念。
- ❌ 对于小型项目而言,引入大量抽象可能显得冗余。
- ❌ 请求/响应周期比 Express/Fastify 稍慢。
4.4 Fastify —— 高性能之选
Fastify 专注于性能和开发者体验,内置 JSON Schema 验证,是目前性能最高的 Node.js Web 框架之一。
// 📁 fastify-app.js
import Fastify from 'fastify';
const app = Fastify({ logger: true });
// 使用 JSON Schema 验证请求
const createUserSchema = {
body: {
type: 'object',
required: ['name'],
properties: {
name: { type: 'string', minLength: 2 },
},
},
response: {
201: {
type: 'object',
properties: {
id: { type: 'number' },
name: { type: 'string' },
},
},
},
};
app.get('/api/users', async (request, reply) => {
return [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];
});
app.get('/api/users/:id', async (request, reply) => {
const { id } = request.params;
return { id: Number(id), name: `User ${id}` };
});
app.post('/api/users', { schema: createUserSchema }, async (request, reply) => {
const { name } = request.body;
reply.code(201);
return { id: Date.now(), name };
});
const start = async () => {
try {
await app.listen({ port: 3000 });
console.log('Fastify 服务已启动');
} catch (err) {
app.log.error(err);
process.exit(1);
}
};
start();Fastify 优缺点:
- ✅ 性能极佳,吞吐量在 Node.js 框架中领先。
- ✅ 内置 Schema 验证(基于 JSON Schema),自动生成文档。
- ✅ 日志系统(Pino)性能出色,内置支持。
- ✅ TypeScript 支持良好。
- ❌ 相比 Express,插件生态较小。
- ❌ 社区资源和教程相对较少。
5. 中间件模式解析
5.1 Express 中间件模式(线性)
Express 中间件是线性顺序执行的链式调用。每个中间件要么结束请求(发送响应),要么调用 next() 将控制权移交给下一个中间件。
// Express 中间件链的详细示例
const express = require('express');
const app = express();
// 中间件 1:全局日志
app.use((req, res, next) => {
console.log(`[1] 日志中间件: ${req.method} ${req.url}`);
next();
});
// 中间件 2:请求计时
app.use((req, res, next) => {
const start = Date.now();
// 猴子补丁(Monkey Patch)res.end 来记录响应时间
const originalEnd = res.end;
res.end = function (...args) {
console.log(`[2] 耗时: ${Date.now() - start}ms`);
originalEnd.apply(res, args);
};
next();
});
// 中间件 3:路径特定的中间件
app.use('/api', (req, res, next) => {
console.log(`[3] API 中间件: 路径前缀 /api`);
next();
});
// 错误处理中间件(4 个参数)
app.use((err, req, res, next) => {
console.error(`[错误处理] ${err.message}`);
res.status(500).json({ error: '服务器错误' });
});
// 注:如果某个中间件调用了 res.send() 或 res.json(),
// 后续中间件将不再执行(除非手动调用 next() 不发送响应)Express 中间件关键特性:
- 全局中间件:通过
app.use()注册,作用于所有路由。 - 路径中间件:
app.use('/path', middleware)仅作用于匹配的路径。 - 路由级中间件:
router.use()作用于特定路由组。 - 错误处理中间件:4 个参数
(err, req, res, next),当有next(err)调用时触发。 - 内置中间件:
express.json()、express.static()等。
Express 中间件的「线性」本质意味着:你调用 next() 之后就不能回到之前的中间件了。这与 Koa 的洋葱模型形成鲜明对比。
5.2 Koa 中间件模式(洋葱模型)
Koa 使用基于 async/await 的洋葱模型。中间件从外到内层层进入,再从内到外层层返回。
// Koa 洋葱模型可视化
const Koa = require('koa');
const app = new Koa();
app.use(async (ctx, next) => {
console.log('➡️ 外层中间件 — 进入');
ctx.state.user = 'Alice'; // 在上下文中传递数据
await next(); // 🔄 进入下一个中间件
console.log('⬅️ 外层中间件 — 返回');
// 此时可以修改响应头等后处理操作
});
app.use(async (ctx, next) => {
console.log(' ➡️ 中层中间件 — 进入');
const start = Date.now();
await next(); // 🔄 进入路由处理
const ms = Date.now() - start;
console.log(` ⬅️ 中层中间件 — 返回 (${ms}ms)`);
ctx.set('X-Response-Time', `${ms}ms`);
});
app.use(async (ctx) => {
console.log(' ➡️ 内层中间件 — 路由处理');
ctx.body = `Hello, ${ctx.state.user}!`;
console.log(' ⬅️ 内层中间件 — 路由完成');
});
// 请求 / 时的输出:
// ➡️ 外层中间件 — 进入
// ➡️ 中层中间件 — 进入
// ➡️ 内层中间件 — 路由处理
// ⬅️ 内层中间件 — 路由完成
// ⬅️ 中层中间件 — 返回 (2ms)
// ⬅️ 外层中间件 — 返回这种模式的优势在于:
- 前后逻辑耦合紧密(如日志计时),代码更优雅。
- 数据通过
ctx.state在整个请求周期中传递。 - 错误处理可以包裹所有中间件。
5.3 NestJS 中间件与装饰器模式
NestJS 的中间件概念被拆分为多种更精细的抽象:
// 1. 函数式中间件
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
console.log(`[${req.method}] ${req.url}`);
next();
}
}
// 2. 守卫(Guard)—— 鉴权逻辑
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
return request.headers.authorization?.startsWith('Bearer ') ?? false;
}
}
// 3. 拦截器(Interceptor)—— 请求/响应转换
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators';
@Injectable()
export class ResponseTimeInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const start = Date.now();
return next.handle().pipe(
tap(() => {
const response = context.switchToHttp().getResponse();
response.setHeader('X-Response-Time', `${Date.now() - start}ms`);
}),
);
}
}
// 4. 管道(Pipe)—— 输入验证与转换
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
@Injectable()
export class ParseIdPipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata) {
const id = parseInt(value, 10);
if (isNaN(id)) {
throw new BadRequestException('ID 必须是数字');
}
return id;
}
}在 NestJS 中,这些概念通过装饰器组合使用,提供了比起传统中间件更细粒度的控制。
6. ORM 与数据库:Prisma vs TypeORM
6.1 Prisma —— 新一代 ORM
Prisma 是目前最流行的 Node.js ORM,以其类型安全和极佳开发者体验著称。
// 📁 prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String
role Role @default(USER)
posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
enum Role {
USER
ADMIN
}// 📁 Prisma 查询示例
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
// 创建用户并关联文章
async function createUserWithPost() {
const user = await prisma.user.create({
data: {
email: 'alice@example.com',
name: 'Alice',
posts: {
create: { title: 'Hello Prisma', content: '这是我的第一篇文章' },
},
},
include: { posts: true },
});
return user;
}
// 复杂查询:查询用户及其文章,并按条件筛选
async function getUsersWithPublishedPosts() {
const users = await prisma.user.findMany({
where: {
posts: {
some: { published: true },
},
},
include: {
posts: {
where: { published: true },
orderBy: { createdAt: 'desc' },
take: 5,
},
},
});
return users;
}
// 分页查询
async function getPaginatedPosts(page: number, pageSize: number) {
const [posts, total] = await Promise.all([
prisma.post.findMany({
skip: (page - 1) * pageSize,
take: pageSize,
orderBy: { createdAt: 'desc' },
include: { author: { select: { name: true, email: true } } },
}),
prisma.post.count(),
]);
return { data: posts, total, page, pageSize, totalPages: Math.ceil(total / pageSize) };
}
// 事务操作
async function transferPoints(fromUserId: number, toUserId: number, points: number) {
await prisma.$transaction([
prisma.user.update({
where: { id: fromUserId },
data: { points: { decrement: points } },
}),
prisma.user.update({
where: { id: toUserId },
data: { points: { increment: points } },
}),
]);
}Prisma 优缺点:
| 优点 | 缺点 |
|---|---|
| ✅ 类型安全 —— 自动生成 TS 类型 | ❌ 编译步骤较慢(generate) |
| ✅ Schema 声明式定义,直观易读 | ❌ 某些复杂查询不如原生 SQL 灵活 |
| ✅ Migration 工具完善 | ❌ 内存占用比 TypeORM 稍大 |
| ✅ 强大的筛选/关联/事务能力 | ❌ 不适合已有复杂数据库的迁移场景 |
| ✅ 优秀的关系查询(include / select) |
6.2 TypeORM —— 老牌 ORM
TypeORM 受 Java 的 Hibernate 和 .NET 的 Entity Framework 影响,采用数据映射(Data Mapper)和主动记录(Active Record)两种模式。
// 📁 TypeORM 实体定义
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
OneToMany,
ManyToOne,
JoinColumn,
} from 'typeorm';
@Entity('users')
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true })
email: string;
@Column()
name: string;
@Column({ type: 'enum', enum: ['USER', 'ADMIN'], default: 'USER' })
role: string;
@OneToMany(() => Post, (post) => post.author)
posts: Post[];
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
@Entity('posts')
export class Post {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@Column({ type: 'text', nullable: true })
content: string;
@Column({ default: false })
published: boolean;
@ManyToOne(() => User, (user) => user.posts)
@JoinColumn({ name: 'author_id' })
author: User;
@Column({ name: 'author_id' })
authorId: number;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}// 📁 TypeORM 查询示例
import { DataSource, Repository } from 'typeorm';
import { User } from './entities/User';
import { Post } from './entities/Post';
const AppDataSource = new DataSource({
type: 'postgres',
url: process.env.DATABASE_URL,
entities: [User, Post],
synchronize: false, // 生产环境务必设为 false
});
// 使用 Repository 模式
const userRepo: Repository<User> = AppDataSource.getRepository(User);
const postRepo: Repository<Post> = AppDataSource.getRepository(Post);
// 创建用户
async function createUser() {
const user = userRepo.create({ email: 'bob@example.com', name: 'Bob' });
return await userRepo.save(user);
}
// 使用 QueryBuilder 构建复杂查询
async function findUsersWithPublishedPosts() {
return await userRepo
.createQueryBuilder('user')
.leftJoinAndSelect('user.posts', 'post')
.where('post.published = :published', { published: true })
.andWhere('user.role = :role', { role: 'USER' })
.orderBy('user.createdAt', 'DESC')
.skip(0)
.take(10)
.getManyAndCount();
}
// 事务
async function updateInTransaction() {
await AppDataSource.transaction(async (manager) => {
await manager.update(User, { id: 1 }, { name: 'Alice Updated' });
await manager.update(Post, { authorId: 1 }, { published: true });
});
}TypeORM 优缺点:
| 优点 | 缺点 |
|---|---|
| ✅ 配置丰富,可定制性强 | ❌ 类型推断不如 Prisma 严格 |
| ✅ 支持多种数据库和迁移 | ❌ 团队维护力度不如 Prisma 活跃 |
| ✅ QueryBuilder 灵活强大 | ❌ 装饰器形式的实体定义冗余 |
| ✅ 支持 Data Mapper 和 Active Record 两种模式 | ❌ 配置项多,上手稍复杂 |
6.3 选型建议
| 场景 | 推荐 | 理由 |
|---|---|---|
| 新项目,追求类型安全 | Prisma | 自动类型生成,Schema 清晰 |
| 已有数据库,需要反向工程 | TypeORM | 实体从数据库同步更方便 |
| 需要 GraphQL 支持 | Prisma | 与 Nexus / GraphQL 集成更佳 |
| 复杂原生 SQL 需求 | TypeORM | QueryBuilder 更灵活 |
| 团队 TypeScript 经验丰富 | Prisma | 类型安全带来更好的协作体验 |
7. 包管理器:npm / yarn / pnpm
7.1 对比总览
| 特性 | npm | yarn Classic | yarn Berry (PnP) | pnpm |
|---|---|---|---|---|
| 首次发布 | 2010 | 2016 | 2020 | 2017 |
| 安装速度 | 中等 | 较快 | 快 | 极快 |
| 磁盘占用 | 高(重复依赖) | 高(重复依赖) | 极低(零安装) | 低(硬链接) |
| 依赖隔离 | 扁平 + 嵌套 | 扁平 + 嵌套 | 严格隔离 | 严格隔离(幽灵依赖) |
| Monorepo 支持 | 有限(workspaces) | workspaces | workspaces | 内置 workspace |
| lock 文件 | package-lock.json | yarn.lock | yarn.lock + .pnp.cjs | pnpm-lock.yaml |
7.2 pnpm —— 推荐之选
pnpm 使用内容可寻址存储,所有依赖包存储在全局仓库中,项目通过硬链接引用,大幅节省磁盘空间。
# 安装 pnpm
npm install -g pnpm
# 初始化项目
pnpm init
# 安装依赖
pnpm add express
pnpm add -D typescript @types/node
pnpm add -g pm2 # 全局安装
# 项目安装(基于 lock 文件)
pnpm install
# 更新依赖
pnpm update
# 运行脚本
pnpm dev
pnpm build
pnpm start# pnpm workspace monorepo
# 📁 pnpm-workspace.yaml
packages:
- 'packages/*'
- 'apps/*'7.3 npm 最佳实践
# npm 安全检查和修复
npm audit
npm audit fix
# 使用 .npmrc 配置
# 📁 .npmrc
save-exact=true # 精确版本号
registry=https://registry.npmmirror.com/ # 国内镜像
# npm workspaces(多包仓库)
# 📁 package.json(根目录)
{
"workspaces": ["packages/*", "apps/*"]
}7.4 yarn 最佳实践
# yarn Berry (v3+) 零安装模式
yarn set version berry
yarn install
# 使用 .yarnrc.yml
# 📁 .yarnrc.yml
nodeLinker: "node-modules" # 传统模式(兼容性更好)
# 或
nodeLinker: "pnp" # PnP 模式(零安装)8. TypeScript 在 Node.js 中的应用
8.1 项目初始化
# 使用 tsx 直接运行(无需编译)
npm install -D typescript @types/node tsx
# 或者使用 ts-node
npm install -D typescript @types/node ts-node// 📁 tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}// 📁 package.json(ESM + TypeScript)
{
"type": "module",
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc",
"start": "node dist/index.js",
"typecheck": "tsc --noEmit"
}
}8.2 TypeScript 实用模式
// 类型安全的 Express Request Handler
import { Request, Response, NextFunction, RequestHandler } from 'express';
// 定义泛型处理函数
type AsyncHandler = <T = any>(
fn: (req: Request, res: Response, next: NextFunction) => Promise<T>
) => RequestHandler;
const asyncHandler: AsyncHandler = (fn) => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
// 自定义 Request 类型(扩展)
declare global {
namespace Express {
interface Request {
user?: {
id: number;
email: string;
role: 'USER' | 'ADMIN';
};
}
}
}
// 使用
app.get(
'/api/users/:id',
authenticate, // 中间件设置 req.user
asyncHandler(async (req, res) => {
const user = req.user!; // 类型安全
res.json(user);
}),
);
// 泛型 API 响应类型
interface ApiResponse<T> {
success: boolean;
data: T;
message?: string;
timestamp: number;
}
function success<T>(data: T, message?: string): ApiResponse<T> {
return { success: true, data, message, timestamp: Date.now() };
}
function error(message: string): ApiResponse<null> {
return { success: false, data: null, message, timestamp: Date.now() };
}
// 使用
app.get('/api/users', (req, res) => {
const users = [{ id: 1, name: 'Alice' }];
res.json(success(users, '获取用户列表成功'));
});8.3 环境变量类型安全
// 📁 src/config/env.ts
import 'dotenv/config';
interface Env {
NODE_ENV: 'development' | 'production' | 'test';
PORT: number;
DATABASE_URL: string;
JWT_SECRET: string;
REDIS_URL?: string;
}
function getEnv(): Env {
return {
NODE_ENV: (process.env.NODE_ENV as Env['NODE_ENV']) ?? 'development',
PORT: Number(process.env.PORT) || 3000,
DATABASE_URL: process.env.DATABASE_URL!,
JWT_SECRET: process.env.JWT_SECRET!,
REDIS_URL: process.env.REDIS_URL,
};
}
export const env = getEnv();9. 生产部署:PM2 与 Docker
9.1 PM2 —— 进程管理器
PM2 是 Node.js 最流行的生产进程管理器,提供负载均衡、自动重启和监控功能。
# 安装 PM2
npm install -g pm2
# 启动应用
pm2 start dist/index.js --name my-app
# 多实例(集群模式,利用多核 CPU)
pm2 start dist/index.js -i max --name my-app
# 常用命令
pm2 list # 查看所有进程
pm2 logs my-app # 查看日志
pm2 monit # 监控面板
pm2 restart my-app # 重启
pm2 reload my-app # 零停机重载
pm2 stop my-app # 停止
pm2 delete my-app # 删除进程
pm2 save # 保存进程列表
pm2 startup # 设置开机自启// 📁 ecosystem.config.cjs
module.exports = {
apps: [{
name: 'my-app',
script: 'dist/index.js',
instances: 'max', // 使用所有 CPU 核心
exec_mode: 'cluster', // 集群模式
env: {
NODE_ENV: 'production',
PORT: 3000,
},
env_development: {
NODE_ENV: 'development',
PORT: 3000,
},
max_memory_restart: '500M', // 超过 500MB 自动重启
log_date_format: 'YYYY-MM-DD HH:mm:ss',
error_file: './logs/error.log',
out_file: './logs/output.log',
merge_logs: true,
autorestart: true, // 崩溃自动重启
watch: false, // 生产环境关闭文件监听
max_restarts: 10, // 1 分钟内最多重启 10 次
restart_delay: 1000, // 重启延迟 1 秒
}],
};# 使用配置文件启动
pm2 start ecosystem.config.cjs --env production
pm2 start ecosystem.config.cjs --env development9.2 Docker 容器化部署
# 📁 Dockerfile
# === 多阶段构建 ===
# 阶段 1:依赖安装和构建
FROM node:20-alpine AS builder
WORKDIR /app
# 优先复制依赖配置,利用 Docker 缓存
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
# 复制源码并构建
COPY tsconfig.json ./
COPY src/ ./src/
RUN pnpm run build
# 阶段 2:生产镜像(最小化)
FROM node:20-alpine AS runner
WORKDIR /app
# 安全:使用非 root 用户
RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 appuser
# 只复制生产依赖和构建产物
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && \
pnpm install --frozen-lockfile --prod && \
pnpm store prune
COPY --from=builder /app/dist ./dist
USER appuser
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
CMD ["node", "dist/index.js"]# 📁 docker-compose.yml
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://user:password@db:5432/myapp
- REDIS_URL=redis://redis:6379
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
restart: unless-stopped
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
db:
image: postgres:16-alpine
volumes:
- pgdata:/var/lib/postgresql/data
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
POSTGRES_DB: myapp
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d myapp"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
redis:
image: redis:7-alpine
volumes:
- redisdata:/data
restart: unless-stopped
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- app
restart: unless-stopped
volumes:
pgdata:
redisdata:# 📁 nginx.conf(反向代理到 Node.js)
events {}
http {
upstream node_app {
server app:3000;
}
server {
listen 80;
location / {
proxy_pass http://node_app;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
# WebSocket 支持
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 超时设置
proxy_connect_timeout 60s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
}
# 静态资源缓存
location /static/ {
proxy_pass http://node_app;
expires 7d;
add_header Cache-Control "public, immutable";
}
}
}9.3 生产环境清单
□ 环境变量管理(.env + 密钥管理服务)
□ 日志集中化管理(Winston + ELK / Loki)
□ 错误监控(Sentry)
□ 健康检查端点(/health)
□ 优雅关闭(Graceful Shutdown)
□ 数据库连接池管理
□ 请求限流(express-rate-limit)
□ CORS 配置
□ HTTPS / SSL 证书
□ 安全 headers(helmet)
□ CI/CD 流水线
□ 单元测试和集成测试
□ 性能基准测试
□ 备份和灾备策略// 📁 优雅关闭示例
import http from 'node:http';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
const server = http.createServer(app);
function gracefulShutdown(signal: string) {
console.log(`收到 ${signal} 信号,开始优雅关闭...`);
server.close(async () => {
console.log('HTTP 服务已关闭');
await prisma.$disconnect();
console.log('数据库连接已关闭');
process.exit(0);
});
// 超时强制关闭
setTimeout(() => {
console.error('强制关闭');
process.exit(1);
}, 30000);
}
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
server.listen(3000);10. 总结与选型建议
10.1 框架选型决策树
项目需求
│
├── 快速原型 / 简单 API
│ └── Express ✅ 或 Fastify
│
├── 中间件密集型 / 追求代码优雅
│ └── Koa
│
├── 大型企业应用 / 长期维护
│ └── NestJS ✅ (强烈推荐)
│
└── 极致性能 / 微服务
└── Fastify ✅10.2 技术栈推荐
小型项目 / 个人项目:
Express + JavaScript + Prisma + pnpm中型项目 / 创业团队:
Fastify / Express + TypeScript + Prisma + pnpm + Docker大型项目 / 企业级:
NestJS + TypeScript + Prisma + pnpm + Docker + CI/CD10.3 学习路线
基础阶段
┌──────────────────────────┐
│ Node.js 核心 API │
│ 事件循环与异步编程 │
│ npm / pnpm 包管理 │
└──────────┬───────────────┘
▼
进阶阶段
┌──────────────────────────┐
│ Express / Koa 框架 │
│ REST API 设计规范 │
│ 中间件与错误处理 │
│ 数据库 ORM │
└──────────┬───────────────┘
▼
高级阶段
┌──────────────────────────┐
│ NestJS + TypeScript │
│ 依赖注入与模块化 │
│ 测试(Jest / Supertest) │
│ Docker 容器化部署 │
└──────────┬───────────────┘
▼
专业阶段
┌──────────────────────────┐
│ 微服务架构 │
│ 消息队列(RabbitMQ / Kafka)│
│ 性能优化与监控 │
│ DevOps / CI/CD │
└──────────────────────────┘10.4 推荐资源
- 官方文档:Node.js 官网 | Express | Koa | NestJS | Fastify
- 数据库:Prisma 文档 | TypeORM 文档
- TypeScript:TypeScript Handbook
- 部署:PM2 文档 | Docker 文档
本文发布于 2024-08-10,最后更新于 2024-08-10。Node.js 生态发展迅速,建议关注各框架官方文档以获取最新信息。