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 版本:
二、快速上手:5分钟写出第一个 Express 应用
1. 初始化项目
1 2
| mkdir express-demo && cd express-demo npm init -y
|
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
| const express = require('express');
const app = express();
app.get('/', (req, res) => { res.send('Hello, Express!'); });
const PORT = 3000; app.listen(PORT, () => { console.log(`Server is running at http://localhost:${PORT}`); });
|
4. 启动应用
打开浏览器访问 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 |
请求方法 |
GET、POST |
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 的核心扩展机制,它是一个函数,可以访问 req、res 和 next(下一个中间件的回调),可以执行以下操作:
- 执行任何代码
- 修改
req/res 对象
- 结束请求-响应循环(如
res.send())
- 调用下一个中间件(
next())
中间件的分类
- 应用级中间件:绑定到
app 上,通过 app.use() 或 app.METHOD() 注册
- 路由级中间件:绑定到
express.Router() 实例上
- 错误处理中间件:有 4 个参数(
err, req, res, next),专门处理错误
- 内置中间件:Express 自带的中间件(如
express.static、express.json)
- 第三方中间件:社区开发的中间件(如
body-parser、cookie-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();
app.use((req, res, next) => { console.log(`[${new Date().toISOString()}] ${req.method} ${req.path}`); next(); });
app.use('/user', (req, res, next) => { console.log('访问了 /user 相关路径'); next(); });
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();
app.get('/', (req, res) => { res.send('首页'); });
app.get('/user', (req, res) => { const name = req.query.name || '访客'; res.send(`用户:${name}`); });
app.get('/user/:id', (req, res) => { const id = req.params.id; res.send(`用户ID:${id}`); });
app.get('/user/:id/post/:postId', (req, res) => { const { id, postId } = req.params; res.send(`用户 ${id} 的文章 ${postId}`); });
app.post('/user', (req, res) => { const body = req.body; res.json({ code: 200, message: '创建用户成功', data: body }); });
app.put('/user/:id', (req, res) => { const id = req.params.id; const body = req.body; res.json({ code: 200, message: `更新用户 ${id} 成功`, data: body }); });
app.delete('/user/:id', (req, res) => { const id = req.params.id; res.json({ code: 200, message: `删除用户 ${id} 成功` }); });
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
| app.use(express.json());
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');
app.use('/user', userRouter);
app.listen(3000);
|
现在访问 /user、/user/123 就会匹配到用户路由模块里的路由了。
五、模板引擎:渲染动态 HTML
Express 可以配合多种模板引擎(如 EJS、Pug、Nunjucks、Handlebars),无需额外中间件(Express 内置了模板引擎支持)。
1. 安装模板引擎
以 EJS(最常用、语法最接近 HTML)为例:
2. 配置模板引擎
在 app.js 中配置:
1 2 3 4 5 6 7 8 9
| const express = require('express'); const path = require('path'); const app = express();
app.set('views', path.join(__dirname, 'views'));
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) => { res.render('index', { title: 'Express 模板引擎', message: 'Hello, EJS!', list: ['苹果', '香蕉', '橙子'], isAdmin: true }); });
|
六、静态资源服务:托管 CSS、JS、图片
Express 内置了 express.static 中间件,无需第三方依赖。
1. 配置静态资源目录
创建 public 文件夹,存放静态文件(如 public/css/style.css、public/js/main.js、public/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();
app.use(express.static(path.join(__dirname, 'public')));
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();
app.use(cookieParser('secret key'));
app.get('/set-cookie', (req, res) => { res.cookie('name', 'express', { maxAge: 86400000, path: '/', httpOnly: true, signed: true }); res.send('Cookie 设置成功'); });
app.get('/get-cookie', (req, res) => { const name = req.cookies.name; const signedName = req.signedCookies.name; res.send(`普通 Cookie:${name},签名 Cookie:${signedName}`); });
app.get('/clear-cookie', (req, res) => { res.clearCookie('name'); res.send('Cookie 清除成功'); });
app.listen(3000);
|
2. Session:存储在服务端
使用 express-session 中间件(生产环境建议配合 connect-mongo 或 connect-redis 存储 Session,避免内存泄漏):
1 2 3
| npm install express-session --save
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'); const app = express();
app.use(session({ secret: 'secret key', resave: false, saveUninitialized: false, cookie: { maxAge: 86400000, httpOnly: true, secure: process.env.NODE_ENV === 'production' }, store: MongoStore.create({ mongoUrl: 'mongodb://localhost:27017/express-demo', ttl: 86400 }) }));
app.get('/set-session', (req, res) => { req.session.user = { id: 1, name: 'express', isAdmin: true }; res.send('Session 设置成功'); });
app.get('/get-session', (req, res) => { const user = req.session.user; if (user) { res.send(`欢迎回来,${user.name}!`); } else { res.send('未登录'); } });
app.get('/clear-session', (req, res) => { req.session.destroy((err) => { if (err) { res.send('Session 清除失败'); } else { res.clearCookie('connect.sid'); 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();
app.get('/', (req, res) => { res.send('首页'); });
app.get('/error', (req, res, next) => { const err = new Error('服务器内部错误'); err.status = 500; next(err); });
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); 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.emit('error', err, req, res); });
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');
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');
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 } });
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 } ); 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);
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'));
|
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 = [ new DailyRotateFile({ filename: 'logs/error-%DATE%.log', datePattern: 'YYYY-MM-DD', level: 'error', maxFiles: '30d' }), 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() ) })); }
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');
app.use(morgan('combined', { stream: { write: (message) => logger.info(message.trim()) } }));
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());
|
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');
const globalLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 100, message: '请求过于频繁,请稍后再试' }); app.use(globalLimiter);
const loginLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 5, message: '登录请求过于频繁,请稍后再试' }); app.post('/login', loginLimiter, (req, res) => { res.send('登录接口'); });
|
十二、部署:生产环境部署
1. 使用 PM2 进程管理
PM2 可以让应用在后台运行,自动重启,负载均衡,日志管理:
基本命令
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 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', 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, 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 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;
location /static { alias /path/to/express-demo/public; expires 30d; }
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
| 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 应用了。