Express

Express 是由 TJ Holowaychuk(后来也是 Koa 的作者)创建、目前由 StrongLoop 维护的 Node.js 最老牌、生态最丰富、开箱即用的 Web 框架,核心代码轻量但内置了路由、静态资源、中间件管理等常用功能,稳定可靠,适合快速开发、企业级项目,很多现代框架(如 NestJS、LoopBack)底层也是基于 Express 的。

本文将从环境准备、快速上手、核心概念,到路由、模板引擎、数据库、错误处理、安全、部署、性能优化,全面讲解 Express 的用法,附完整可运行代码,帮你快速掌握 Express。


一、前置准备:环境要求

Express 对 Node.js 版本要求较低,几乎所有主流 LTS 版本都支持:

  • 推荐:Node.js v14.x+(最新 LTS,性能更好)
  • 最低兼容:Node.js v0.10.x+(但建议升级)

检查 Node.js 和 npm 版本:

1
2
node -v
npm -v

二、快速上手:5分钟写出第一个 Express 应用

1. 初始化项目

1
2
mkdir express-demo && cd express-demo
npm init -y # 快速生成 package.json

2. 安装 Express

1
npm install express --save

3. 编写 Hello World 应用

创建 app.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 1. 引入 Express
const express = require('express');

// 2. 实例化 Express 应用
const app = express();

// 3. 定义路由:GET 请求根路径
app.get('/', (req, res) => {
// req:请求对象,res:响应对象
res.send('Hello, Express!');
});

// 4. 监听端口
const PORT = 3000;
app.listen(PORT, () => {
console.log(`Server is running at http://localhost:${PORT}`);
});

4. 启动应用

1
node app.js

打开浏览器访问 http://localhost:3000,就能看到 Hello, Express! 了。


三、核心概念:理解 Express 的基础

Express 的核心只有三个:应用实例(app)请求/响应对象(req/res)中间件(Middleware)

1. 应用实例(app)

app 是 Express 的核心对象,通过它可以:

  • 定义路由(app.get()app.post()app.all() 等)
  • 注册中间件(app.use()
  • 配置应用(app.set()app.get()
  • 启动服务器(app.listen()

2. 请求/响应对象(req/res)

Express 封装了 Node.js 原生的 req/res,提供了更简洁的 API。

常用 req 属性/方法

属性/方法 说明 示例
req.query GET 查询参数(对象) req.query.name
req.params 路由动态参数 req.params.id
req.body POST/PUT 请求体(需配合 body-parser 中间件) -
req.cookies Cookie(需配合 cookie-parser 中间件) req.cookies.name
req.session Session(需配合 express-session 中间件) req.session.user
req.method 请求方法 GETPOST
req.path 请求路径(不含查询参数) /user/123
req.get(header) 获取请求头 req.get('Content-Type')

常用 res 属性/方法

属性/方法 说明 示例
res.send(data) 发送响应(自动识别类型:字符串、JSON、Buffer 等) res.send({ code: 200 })
res.json(data) 发送 JSON 响应(更明确) res.json({ code: 200 })
res.render(view, data) 渲染模板(需配合模板引擎) res.render('index', { title: 'Express' })
res.redirect(url) 重定向 res.redirect('/user')
res.status(code) 设置响应状态码 res.status(404).send('Not Found')
res.set(header, value) 设置响应头 res.set('Content-Type', 'text/html')
res.cookie(name, value, options) 设置 Cookie res.cookie('name', 'express', { maxAge: 86400000 })
res.clearCookie(name) 清除 Cookie res.clearCookie('name')

3. 中间件(Middleware)

中间件是 Express 的核心扩展机制,它是一个函数,可以访问 reqresnext(下一个中间件的回调),可以执行以下操作:

  • 执行任何代码
  • 修改 req/res 对象
  • 结束请求-响应循环(如 res.send()
  • 调用下一个中间件(next()

中间件的分类

  1. 应用级中间件:绑定到 app 上,通过 app.use()app.METHOD() 注册
  2. 路由级中间件:绑定到 express.Router() 实例上
  3. 错误处理中间件:有 4 个参数(err, req, res, next),专门处理错误
  4. 内置中间件:Express 自带的中间件(如 express.staticexpress.json
  5. 第三方中间件:社区开发的中间件(如 body-parsercookie-parser

应用级中间件示例

创建 middleware-demo.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const express = require('express');
const app = express();

// 1. 全局中间件:所有请求都会经过
app.use((req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.path}`);
next(); // 必须调用 next() 进入下一个中间件
});

// 2. 路由级中间件(绑定到特定路径)
app.use('/user', (req, res, next) => {
console.log('访问了 /user 相关路径');
next();
});

// 3. 路由中间件(绑定到特定方法和路径)
app.get('/', (req, res) => {
res.send('首页');
});

app.get('/user', (req, res) => {
res.send('用户列表');
});

app.listen(3000);

四、路由:处理不同 URL 请求

Express 内置了强大的路由系统,无需第三方中间件(和 Koa2 不同)。

1. 基本路由用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
const express = require('express');
const app = express();

// 1. GET 请求
app.get('/', (req, res) => {
res.send('首页');
});

// 2. GET 请求带查询参数:/user?name=express
app.get('/user', (req, res) => {
const name = req.query.name || '访客';
res.send(`用户:${name}`);
});

// 3. 动态路由:/user/123
app.get('/user/:id', (req, res) => {
const id = req.params.id;
res.send(`用户ID:${id}`);
});

// 4. 动态路由带多个参数:/user/:id/post/:postId
app.get('/user/:id/post/:postId', (req, res) => {
const { id, postId } = req.params;
res.send(`用户 ${id} 的文章 ${postId}`);
});

// 5. POST 请求(需配合 express.json() 解析请求体)
app.post('/user', (req, res) => {
const body = req.body;
res.json({
code: 200,
message: '创建用户成功',
data: body
});
});

// 6. PUT 请求:更新资源
app.put('/user/:id', (req, res) => {
const id = req.params.id;
const body = req.body;
res.json({ code: 200, message: `更新用户 ${id} 成功`, data: body });
});

// 7. DELETE 请求:删除资源
app.delete('/user/:id', (req, res) => {
const id = req.params.id;
res.json({ code: 200, message: `删除用户 ${id} 成功` });
});

// 8. app.all():匹配所有 HTTP 方法
app.all('/secret', (req, res) => {
res.send('这是一个秘密路径,所有方法都能访问');
});

app.listen(3000);

2. 解析请求体:内置中间件

Express 4.16.0+ 内置了 express.json()express.urlencoded(),替代了之前的 body-parser

1
2
3
4
5
6
// 解析 application/json 格式的请求体
app.use(express.json());

// 解析 application/x-www-form-urlencoded 格式的请求体(表单提交)
// extended: true 表示可以解析复杂对象(如嵌套对象)
app.use(express.urlencoded({ extended: true }));

3. 路由模块化:express.Router()

当路由越来越多时,把所有路由写在 app.js 里会很乱,Express 提供了 express.Router() 来模块化路由。

示例:用户路由模块

创建 routes/user.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const express = require('express');
const router = express.Router(); // 实例化路由

// 用户列表
router.get('/', (req, res) => {
res.send('用户列表');
});

// 用户详情
router.get('/:id', (req, res) => {
res.send(`用户 ${req.params.id} 的详情`);
});

// 创建用户
router.post('/', (req, res) => {
res.send('创建用户');
});

module.exports = router; // 导出路由

在 app.js 中注册路由模块

1
2
3
4
5
6
7
8
const express = require('express');
const app = express();
const userRouter = require('./routes/user'); // 引入用户路由

// 注册用户路由,统一前缀为 /user
app.use('/user', userRouter);

app.listen(3000);

现在访问 /user/user/123 就会匹配到用户路由模块里的路由了。


五、模板引擎:渲染动态 HTML

Express 可以配合多种模板引擎(如 EJS、Pug、Nunjucks、Handlebars),无需额外中间件(Express 内置了模板引擎支持)。

1. 安装模板引擎

EJS(最常用、语法最接近 HTML)为例:

1
npm install ejs --save

2. 配置模板引擎

app.js 中配置:

1
2
3
4
5
6
7
8
9
const express = require('express');
const path = require('path'); // 引入 Node.js 内置的 path 模块
const app = express();

// 1. 设置模板文件存放目录(默认是 views)
app.set('views', path.join(__dirname, 'views'));

// 2. 设置模板引擎
app.set('view engine', 'ejs');

3. 编写 EJS 模板

创建 views/index.ejs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title><%= title %></title>
<style>
body { font-family: Arial, sans-serif; padding: 20px; }
li { margin: 10px 0; }
</style>
</head>
<body>
<h1><%= title %></h1>
<p><%= message %></p>

<!-- 循环 -->
<h3>水果列表</h3>
<ul>
<% list.forEach(item => { %>
<li><%= item %></li>
<% }) %>
</ul>

<!-- 条件判断 -->
<% if (isAdmin) { %>
<p style="color: red;">欢迎管理员!</p>
<% } %>
</body>
</html>

4. 渲染模板

app.js 中添加路由:

1
2
3
4
5
6
7
8
9
app.get('/', (req, res) => {
// 渲染 views/index.ejs,传递数据
res.render('index', {
title: 'Express 模板引擎',
message: 'Hello, EJS!',
list: ['苹果', '香蕉', '橙子'],
isAdmin: true
});
});

六、静态资源服务:托管 CSS、JS、图片

Express 内置了 express.static 中间件,无需第三方依赖。

1. 配置静态资源目录

创建 public 文件夹,存放静态文件(如 public/css/style.csspublic/js/main.jspublic/images/logo.png):

1
2
3
4
5
6
7
8
9
10
11
12
const express = require('express');
const path = require('path');
const app = express();

// 配置静态资源目录:访问 /css/style.css 会映射到 public/css/style.css
app.use(express.static(path.join(__dirname, 'public')));

// 也可以给静态资源添加统一前缀(如 /static)
// app.use('/static', express.static(path.join(__dirname, 'public')));
// 此时访问 /static/css/style.css 会映射到 public/css/style.css

app.listen(3000);

七、Cookie 与 Session:用户状态管理

1. Cookie:存储在客户端

使用 cookie-parser 中间件:

1
npm install cookie-parser --save

配置与使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const express = require('express');
const cookieParser = require('cookie-parser');
const app = express();

// 注册 cookie-parser 中间件
app.use(cookieParser('secret key')); // secret key 用于签名 Cookie,防止篡改

// 设置 Cookie
app.get('/set-cookie', (req, res) => {
// 普通 Cookie
res.cookie('name', 'express', {
maxAge: 86400000, // 过期时间:1天(毫秒)
path: '/', // 生效路径
httpOnly: true, // 仅 HTTP 访问,防止 XSS 窃取
signed: true // 签名 Cookie
});
res.send('Cookie 设置成功');
});

// 获取 Cookie
app.get('/get-cookie', (req, res) => {
const name = req.cookies.name; // 普通 Cookie
const signedName = req.signedCookies.name; // 签名 Cookie
res.send(`普通 Cookie:${name},签名 Cookie:${signedName}`);
});

// 清除 Cookie
app.get('/clear-cookie', (req, res) => {
res.clearCookie('name');
res.send('Cookie 清除成功');
});

app.listen(3000);

2. Session:存储在服务端

使用 express-session 中间件(生产环境建议配合 connect-mongoconnect-redis 存储 Session,避免内存泄漏):

1
2
3
npm install express-session --save
# 生产环境可选:存储到 MongoDB
npm install connect-mongo --save

配置与使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
const express = require('express');
const session = require('express-session');
const MongoStore = require('connect-mongo'); // 可选:MongoDB 存储
const app = express();

// 注册 express-session 中间件
app.use(session({
secret: 'secret key', // 签名 Session ID 的密钥
resave: false, // 强制重新保存 Session,即使没有修改(建议 false)
saveUninitialized: false, // 强制保存未初始化的 Session(建议 false,节省资源)
cookie: {
maxAge: 86400000, // 过期时间:1天
httpOnly: true,
secure: process.env.NODE_ENV === 'production' // 生产环境仅 HTTPS 访问
},
// 可选:存储到 MongoDB(生产环境推荐)
store: MongoStore.create({
mongoUrl: 'mongodb://localhost:27017/express-demo',
ttl: 86400 // 过期时间:1天(秒)
})
}));

// 设置 Session
app.get('/set-session', (req, res) => {
req.session.user = { id: 1, name: 'express', isAdmin: true };
res.send('Session 设置成功');
});

// 获取 Session
app.get('/get-session', (req, res) => {
const user = req.session.user;
if (user) {
res.send(`欢迎回来,${user.name}!`);
} else {
res.send('未登录');
}
});

// 清除 Session
app.get('/clear-session', (req, res) => {
req.session.destroy((err) => {
if (err) {
res.send('Session 清除失败');
} else {
res.clearCookie('connect.sid'); // 清除 Session ID 的 Cookie
res.send('Session 清除成功');
}
});
});

app.listen(3000);

八、错误处理:统一处理异常

Express 的错误处理中间件有 4 个参数err, req, res, next),必须放在所有路由和中间件的最后面

1. 基本错误处理

创建 error-demo.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
const express = require('express');
const app = express();

// 1. 普通中间件
app.get('/', (req, res) => {
res.send('首页');
});

// 2. 模拟错误的路由
app.get('/error', (req, res, next) => {
// 手动抛出错误,传递给错误处理中间件
const err = new Error('服务器内部错误');
err.status = 500;
next(err);
});

// 3. 404 中间件(放在所有路由之后,错误处理之前)
app.use((req, res, next) => {
const err = new Error('页面不存在');
err.status = 404;
next(err);
});

// 4. 错误处理中间件(必须放在最后面,4个参数)
app.use((err, req, res, next) => {
// 设置状态码,默认 500
res.status(err.status || 500);

// 根据环境返回不同的响应
if (process.env.NODE_ENV === 'production') {
// 生产环境:不返回错误堆栈
res.json({
code: err.status || 500,
message: err.message || '服务器内部错误'
});
} else {
// 开发环境:返回错误堆栈,方便调试
res.json({
code: err.status || 500,
message: err.message || '服务器内部错误',
stack: err.stack
});
}

// 触发 app 的 error 事件
app.emit('error', err, req, res);
});

// 5. 监听 error 事件,打印日志
app.on('error', (err, req, res) => {
console.error('服务器错误:', err);
});

app.listen(3000);

九、数据库连接:以 MongoDB + Mongoose 为例

Express 可以配合各种数据库,这里以 MongoDB + Mongoose(最常用的 MongoDB ODM)为例。

1. 安装依赖

1
npm install mongoose --save

2. 连接数据库

创建 config/db.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const mongoose = require('mongoose');

// 连接 MongoDB
const connectDB = async () => {
try {
await mongoose.connect('mongodb://localhost:27017/express-demo', {
useNewUrlParser: true,
useUnifiedTopology: true
});
console.log('MongoDB 连接成功!');
} catch (err) {
console.error('MongoDB 连接错误:', err);
process.exit(1); // 连接失败,退出进程
}
};

module.exports = connectDB;

3. 定义 Schema 和 Model

创建 models/User.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const mongoose = require('mongoose');

// 定义 Schema
const UserSchema = new mongoose.Schema({
name: {
type: String,
required: [true, '姓名不能为空'],
minlength: [2, '姓名至少2个字符'],
maxlength: [20, '姓名最多20个字符']
},
age: {
type: Number,
default: 18,
min: [1, '年龄至少1岁'],
max: [120, '年龄最多120岁']
},
email: {
type: String,
required: [true, '邮箱不能为空'],
unique: true, // 唯一索引
match: [/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/, '邮箱格式不正确']
},
createdAt: {
type: Date,
default: Date.now
}
});

// 导出 Model
module.exports = mongoose.model('User', UserSchema);

4. 在路由中使用

创建 routes/user.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
const express = require('express');
const router = express.Router();
const User = require('../models/User');

// 创建用户
router.post('/', async (req, res, next) => {
try {
const user = await User.create(req.body);
res.status(201).json({
code: 201,
message: '创建用户成功',
data: user
});
} catch (err) {
next(err); // 传递给错误处理中间件
}
});

// 查询用户列表
router.get('/', async (req, res, next) => {
try {
const users = await User.find().sort({ createdAt: -1 }); // 按创建时间倒序
res.json({
code: 200,
data: users
});
} catch (err) {
next(err);
}
});

// 查询用户详情
router.get('/:id', async (req, res, next) => {
try {
const user = await User.findById(req.params.id);
if (!user) {
const err = new Error('用户不存在');
err.status = 404;
throw err;
}
res.json({
code: 200,
data: user
});
} catch (err) {
next(err);
}
});

// 更新用户
router.put('/:id', async (req, res, next) => {
try {
const user = await User.findByIdAndUpdate(
req.params.id,
req.body,
{ new: true, runValidators: true } // new: 返回更新后的文档;runValidators: 运行验证规则
);
if (!user) {
const err = new Error('用户不存在');
err.status = 404;
throw err;
}
res.json({
code: 200,
message: '更新用户成功',
data: user
});
} catch (err) {
next(err);
}
});

// 删除用户
router.delete('/:id', async (req, res, next) => {
try {
const user = await User.findByIdAndDelete(req.params.id);
if (!user) {
const err = new Error('用户不存在');
err.status = 404;
throw err;
}
res.json({
code: 200,
message: '删除用户成功'
});
} catch (err) {
next(err);
}
});

module.exports = router;

5. 在 app.js 中注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const express = require('express');
const connectDB = require('./config/db');
const userRouter = require('./routes/user');
const app = express();

// 连接数据库
connectDB();

// 解析请求体
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// 注册路由
app.use('/user', userRouter);

// 404 中间件
app.use((req, res, next) => {
const err = new Error('页面不存在');
err.status = 404;
next(err);
});

// 错误处理中间件
app.use((err, req, res, next) => {
res.status(err.status || 500);
res.json({
code: err.status || 500,
message: err.message || '服务器内部错误',
...(process.env.NODE_ENV !== 'production' && { stack: err.stack })
});
});

app.listen(3000);

十、日志:记录请求与错误

1. 开发环境:morgan

简单的请求日志,开发时用:

1
npm install morgan --save
1
2
3
const morgan = require('morgan');
app.use(morgan('dev')); // 'dev' 格式:彩色输出,简洁明了
// 其他格式:'combined'(Apache 格式,详细)、'common'、'tiny'

2. 生产环境:winston

更强大的日志库,支持分级、文件存储、日志轮转:

1
npm install winston winston-daily-rotate-file --save

创建 utils/logger.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
const winston = require('winston');
const DailyRotateFile = require('winston-daily-rotate-file');

// 定义日志格式
const format = winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
winston.format.errors({ stack: true }),
winston.format.splat(),
winston.format.json()
);

// 定义传输方式
const transports = [
// 错误日志:每天一个文件,保留 30 天
new DailyRotateFile({
filename: 'logs/error-%DATE%.log',
datePattern: 'YYYY-MM-DD',
level: 'error',
maxFiles: '30d'
}),
// 所有日志:每天一个文件,保留 30 天
new DailyRotateFile({
filename: 'logs/combined-%DATE%.log',
datePattern: 'YYYY-MM-DD',
maxFiles: '30d'
})
];

// 开发环境同时输出到控制台
if (process.env.NODE_ENV !== 'production') {
transports.push(new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
}));
}

// 创建 logger 实例
const logger = winston.createLogger({
level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
format,
transports
});

module.exports = logger;

app.js 中使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
const logger = require('./utils/logger');

// 替换 morgan,用 winston 记录请求日志
app.use(morgan('combined', {
stream: {
write: (message) => logger.info(message.trim())
}
}));

// 替换 app.on('error'),用 winston 记录错误日志
app.on('error', (err, req, res) => {
logger.error('服务器错误:', err);
});

十一、安全:常见安全防护

1. 设置安全头:helmet

1
npm install helmet --save
1
2
const helmet = require('helmet');
app.use(helmet()); // 放在最前面,自动设置各种安全头(X-XSS-Protection、X-Frame-Options、Content-Security-Policy 等)

2. 防 XSS:xss-clean

1
npm install xss-clean --save
1
2
const xss = require('xss-clean');
app.use(xss()); // 放在解析请求体之后,路由之前

3. 防 SQL/NoSQL 注入:express-mongo-sanitize(MongoDB 专用)

1
npm install express-mongo-sanitize --save
1
2
const mongoSanitize = require('express-mongo-sanitize');
app.use(mongoSanitize()); // 放在解析请求体之后,路由之前

4. 限制请求频率:express-rate-limit

防止暴力破解、DDoS 攻击:

1
npm install express-rate-limit --save
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const rateLimit = require('express-rate-limit');

// 全局限制:15 分钟内最多 100 次请求
const globalLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 分钟
max: 100,
message: '请求过于频繁,请稍后再试'
});
app.use(globalLimiter);

// 特定路由限制:登录接口 15 分钟内最多 5 次请求
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
message: '登录请求过于频繁,请稍后再试'
});
app.post('/login', loginLimiter, (req, res) => {
res.send('登录接口');
});

十二、部署:生产环境部署

1. 使用 PM2 进程管理

PM2 可以让应用在后台运行,自动重启,负载均衡,日志管理:

1
npm install pm2 -g  # 全局安装

基本命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 启动应用
pm2 start app.js --name express-demo

# 查看应用列表
pm2 list

# 查看日志
pm2 logs express-demo
pm2 logs express-demo --lines 100 # 查看最后 100 行
pm2 logs express-demo --err # 仅查看错误日志

# 重启应用
pm2 restart express-demo

# 停止应用
pm2 stop express-demo

# 删除应用
pm2 delete express-demo

# 监控应用
pm2 monit

# 生成启动脚本(开机自启)
pm2 startup
pm2 save

配置 PM2 配置文件

创建 ecosystem.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
module.exports = {
apps: [{
name: 'express-demo',
script: './app.js',
instances: 'max', // 利用多核 CPU,启动最大实例数
exec_mode: 'cluster', // 集群模式
env: {
NODE_ENV: 'development',
PORT: 3000
},
env_production: {
NODE_ENV: 'production',
PORT: 3000
},
error_file: './logs/pm2-error.log',
out_file: './logs/pm2-out.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss',
merge_logs: true,
min_uptime: '10s',
max_restarts: 10,
autorestart: true,
watch: false, // 生产环境关闭 watch,避免性能问题
ignore_watch: ['node_modules', 'logs']
}]
};

启动生产环境:

1
pm2 start ecosystem.config.js --env production

2. 反向代理:Nginx

生产环境建议用 Nginx 作为反向代理,处理静态资源、负载均衡、HTTPS、Gzip 压缩等:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
server {
listen 80;
server_name your-domain.com;

# 日志
access_log /var/log/nginx/express-demo-access.log;
error_log /var/log/nginx/express-demo-error.log;

# Gzip 压缩
gzip on;
gzip_min_length 1k;
gzip_buffers 16 64k;
gzip_http_version 1.1;
gzip_comp_level 6;
gzip_types text/plain text/css application/javascript text/xml application/xml application/xml+rss application/json;
gzip_vary on;

# 静态资源:直接由 Nginx 处理,不经过 Express
location /static {
alias /path/to/express-demo/public;
expires 30d; # 缓存 30 天
}

# 动态请求:转发给 Express
location / {
proxy_pass http://127.0.0.1:3000;
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;
}
}

十三、性能优化:提升应用性能

1. 启用集群模式

用 PM2 的 cluster 模式,利用多核 CPU:

1
2
3
4
5
6
7
8
9
// ecosystem.config.js
module.exports = {
apps: [{
name: 'express-demo',
script: './app.js',
instances: 'max',
exec_mode: 'cluster'
}]
};

2. 静态资源由 Nginx 处理

如上面的 Nginx 配置所示,静态资源(CSS、JS、图片)直接由 Nginx 处理,不经过 Express,提升性能。

3. 启用缓存

  • 浏览器缓存:设置 Cache-Control 响应头
  • 服务端缓存:用 redis 缓存热点数据(如用户信息、文章列表)

4. 压缩响应

  • Gzip 压缩:Nginx 或 Express 的 compression 中间件
    1
    npm install compression --save
    1
    2
    const compression = require('compression');
    app.use(compression()); // 放在最前面

5. 避免同步操作

所有 I/O 操作(数据库、文件、网络)都用异步方式(async/await 或 Promise),避免阻塞事件循环。


十四、Express vs Koa2:简单对比

特性 Express Koa2
异步处理 回调函数或 Promise async/await,无回调地狱
中间件模型 线性模型 洋葱模型,更灵活
核心体积 较重,内置路由、静态资源等 极轻量(~1500行),无内置功能
扩展性 内置功能多,开箱即用 完全靠中间件扩展,灵活
学习曲线 较低(开箱即用) 稍高(需理解洋葱模型)
生态 最丰富,社区最活跃 丰富,但不如 Express
适用场景 快速开发、企业级项目、遗留系统 学习中间件原理、追求极致轻量、新项目

十五、总结

Express 是一个非常成熟、稳定、生态丰富的 Node.js Web 框架,它的开箱即用、简单易用是最大的优势,适合快速开发、企业级项目。掌握本文的内容,已经可以用 Express 开发一个完整的 Web 应用了。


Express
https://cszy.top/2017-06-06 Express/
作者
csorz
发布于
2017年6月6日
许可协议