Node.js 开发指南 / 第 12 章 · Express 框架
第 12 章 · Express 框架
12.1 Express 简介
Express 是 Node.js 最流行的 Web 框架,提供了简洁的 API 来构建 Web 应用和 API。
# 安装
npm init -y
npm install express
# 推荐同时安装
npm install cors helmet morgan compression
Hello Express
const express = require('express');
const app = express();
const PORT = 3000;
app.get('/', (req, res) => {
res.json({ message: 'Hello, Express!' });
});
app.listen(PORT, () => {
console.log(`服务器运行在 http://localhost:${PORT}/`);
});
Express vs 原生 http
| 特性 | 原生 http | Express |
|---|---|---|
| 路由 | 手动解析 URL | 声明式路由 |
| 中间件 | 手动实现 | 丰富的中间件生态 |
| 请求体解析 | 手动读取流 | express.json() 内置 |
| 错误处理 | 手动 try/catch | 错误中间件 |
| 模板引擎 | 不支持 | 多种引擎支持 |
| 静态文件 | 手动实现 | express.static() |
12.2 路由
基本路由
const express = require('express');
const app = express();
// GET 请求
app.get('/', (req, res) => {
res.send('首页');
});
// POST 请求
app.post('/api/users', (req, res) => {
res.status(201).json({ created: true });
});
// PUT 请求
app.put('/api/users/:id', (req, res) => {
res.json({ updated: req.params.id });
});
// DELETE 请求
app.delete('/api/users/:id', (req, res) => {
res.json({ deleted: req.params.id });
});
// 所有 HTTP 方法
app.all('/api/health', (req, res) => {
res.json({ status: 'ok', method: req.method });
});
路由参数与查询参数
// 路由参数
app.get('/api/users/:id', (req, res) => {
const { id } = req.params;
res.json({ id });
});
// 可选参数
app.get('/api/posts/:category/:id?', (req, res) => {
const { category, id } = req.params;
res.json({ category, id: id || 'all' });
});
// 正则参数
app.get(/\/api\/files\/(.+)/, (req, res) => {
const filePath = req.params[0];
res.json({ path: filePath });
});
// 查询参数
app.get('/api/search', (req, res) => {
const { q, page = 1, limit = 10 } = req.query;
res.json({ query: q, page: Number(page), limit: Number(limit) });
});
Router 模块化
// routes/users.js
const { Router } = require('express');
const router = Router();
router.get('/', (req, res) => {
res.json({ users: [] });
});
router.get('/:id', (req, res) => {
res.json({ id: req.params.id });
});
router.post('/', (req, res) => {
res.status(201).json({ created: true });
});
module.exports = router;
// app.js
const express = require('express');
const app = express();
const userRoutes = require('./routes/users');
app.use('/api/users', userRoutes);
// GET /api/users → 用户列表
// GET /api/users/:id → 单个用户
// POST /api/users → 创建用户
12.3 中间件
中间件执行流程
请求 → 中间件1 → 中间件2 → ... → 路由处理 → 响应
│ │ │
↓ ↓ ↓
next() next() 发送响应
内置中间件
// 解析 JSON 请求体
app.use(express.json({ limit: '10mb' }));
// 解析 URL 编码的请求体
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// 静态文件服务
app.use(express.static('public'));
app.use('/uploads', express.static('uploads'));
app.use(express.static('public', {
maxAge: '1d', // 缓存时间
etag: true, // ETag 支持
lastModified: true, // Last-Modified 支持
}));
自定义中间件
// 日志中间件
function logger(req, res, next) {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`${req.method} ${req.url} ${res.statusCode} ${duration}ms`);
});
next();
}
app.use(logger);
// 认证中间件
function authenticate(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: '未提供认证令牌' });
}
try {
const payload = verifyToken(token);
req.user = payload;
next();
} catch (err) {
res.status(401).json({ error: '令牌无效' });
}
}
// 路由级别中间件
app.get('/api/profile', authenticate, (req, res) => {
res.json({ user: req.user });
});
// 路由组中间件
app.use('/api/admin', authenticate, adminRouter);
第三方中间件
const cors = require('cors');
const helmet = require('helmet');
const morgan = require('morgan');
const compression = require('compression');
// CORS — 跨域资源共享
app.use(cors({
origin: ['https://example.com', 'http://localhost:3001'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
credentials: true,
}));
// Helmet — 安全头部
app.use(helmet());
// Morgan — HTTP 请求日志
app.use(morgan('combined'));
// 或自定义格式
app.use(morgan(':method :url :status :response-time ms'));
// Compression — 响应压缩
app.use(compression());
12.4 请求与响应增强
// 请求处理
app.post('/api/users', express.json(), (req, res) => {
console.log('Body:', req.body);
console.log('Content-Type:', req.get('Content-Type'));
console.log('IP:', req.ip);
console.log('是否 XHR:', req.xhr);
console.log('协议:', req.protocol);
console.log('是否安全:', req.secure);
});
// 响应方法
app.get('/api/data', (req, res) => {
// JSON 响应
res.json({ data: 'hello' });
// 或
// res.send('文本响应');
// res.send(Buffer.from('二进制'));
// res.sendFile('/path/to/file.html');
// res.download('/path/to/file.pdf');
// res.redirect(301, '/new-url');
// res.status(404).json({ error: 'Not found' });
});
// 链式调用
app.get('/api/chain', (req, res) => {
res
.status(200)
.set({
'X-Custom': 'value',
'Cache-Control': 'max-age=3600',
})
.json({ ok: true });
});
12.5 错误处理
// 错误处理中间件(4 个参数)
app.use((err, req, res, next) => {
console.error(err.stack);
const status = err.status || 500;
const message = process.env.NODE_ENV === 'production'
? 'Internal Server Error'
: err.message;
res.status(status).json({
error: message,
...(process.env.NODE_ENV !== 'production' && { stack: err.stack }),
});
});
// 404 处理(放在所有路由之后)
app.use((req, res) => {
res.status(404).json({ error: `Cannot ${req.method} ${req.url}` });
});
// 异步错误处理
const asyncHandler = (fn) => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
app.get('/api/data', asyncHandler(async (req, res) => {
const data = await fetchData(); // 如果抛错,会被错误中间件捕获
res.json(data);
}));
// 自定义错误类
class AppError extends Error {
constructor(message, status) {
super(message);
this.status = status;
this.name = 'AppError';
}
}
app.get('/api/users/:id', asyncHandler(async (req, res) => {
const user = await findUser(req.params.id);
if (!user) {
throw new AppError('用户不存在', 404);
}
res.json(user);
}));
12.6 模板引擎
const express = require('express');
const app = express();
// 设置模板引擎
app.set('view engine', 'ejs');
app.set('views', './views');
// EJS 模板示例
app.get('/', (req, res) => {
res.render('index', {
title: '首页',
users: ['Alice', 'Bob', 'Charlie'],
currentTime: new Date().toLocaleString('zh-CN'),
});
});
<!-- views/index.ejs -->
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
</head>
<body>
<h1><%= title %></h1>
<p>当前时间: <%= currentTime %></p>
<h2>用户列表</h2>
<ul>
<% users.forEach(user => { %>
<li><%= user %></li>
<% }); %>
</ul>
<% if (users.length === 0) { %>
<p>暂无用户</p>
<% } %>
</body>
</html>
12.7 实战项目结构
project/
├── package.json
├── .env
├── src/
│ ├── app.js # Express 应用配置
│ ├── server.js # 服务器启动
│ ├── config/
│ │ └── index.js # 配置管理
│ ├── middleware/
│ │ ├── auth.js # 认证中间件
│ │ ├── logger.js # 日志中间件
│ │ └── errorHandler.js # 错误处理
│ ├── routes/
│ │ ├── index.js # 路由汇总
│ │ ├── users.js # 用户路由
│ │ └── posts.js # 文章路由
│ ├── controllers/
│ │ ├── userController.js
│ │ └── postController.js
│ ├── services/
│ │ ├── userService.js
│ │ └── postService.js
│ ├── models/
│ │ ├── User.js
│ │ └── Post.js
│ └── utils/
│ └── errors.js
├── views/ # 模板文件
├── public/ # 静态资源
└── tests/ # 测试文件
// src/app.js
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const morgan = require('morgan');
const routes = require('./routes');
const errorHandler = require('./middleware/errorHandler');
const app = express();
// 中间件
app.use(helmet());
app.use(cors());
app.use(morgan('dev'));
app.use(express.json());
app.use(express.static('public'));
// 路由
app.use('/api', routes);
// 错误处理
app.use(errorHandler);
module.exports = app;
// src/server.js
const app = require('./app');
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`服务器运行在端口 ${PORT}`);
});
注意事项
⚠️ 中间件顺序很重要:Express 中间件按注册顺序执行,日志和安全中间件应放在路由之前。
⚠️ 异步错误不会自动捕获:Express 4.x 不会自动捕获 async 函数的错误,需要包装
asyncHandler或使用 Express 5。
⚠️ 不要在生产环境暴露错误栈:
err.stack包含敏感信息,生产环境应只返回通用错误消息。
⚠️ 设置请求体大小限制:
express.json({ limit: '10mb' })防止大请求体攻击。
业务场景
- RESTful API 服务:使用 Router 模块化组织 API 路由
- BFF 层:为前端应用聚合多个后端服务的数据
- 管理后台:配合模板引擎构建服务端渲染的管理界面
- Webhook 接收端:接收第三方平台的回调通知
扩展阅读
上一章:第 11 章 · HTTP 服务与客户端 下一章:第 13 章 · REST API 设计 — REST 设计原则、CRUD、版本控制和分页。