Node.js Express 框架完全指南
Express 框架简介
Express 是 Node.js 最流行的 Web 框架,以其简洁、灵活和强大的中间件机制著称。它本身非常轻量,核心功能(路由、中间件)只有约 2000 行代码,其余功能通过中间件扩展实现。
项目初始化与基础结构
mkdir my-express-app && cd my-express-app
npm init -y
npm install express
npm install --save-dev nodemon # 开发时自动重启
// app.js - 最小 Express 应用
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
// 内置中间件:解析 JSON 请求体
app.use(express.json());
// 内置中间件:解析 URL-encoded 请求体(表单提交)
app.use(express.urlencoded({ extended: true }));
// 内置中间件:静态文件服务(public 目录)
app.use(express.static('public'));
// 自定义中间件:日志记录
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`${req.method} ${req.originalUrl} - ${res.statusCode} [${duration}ms]`);
});
next(); // 必须调用 next() 传递控制权
});
// 路由
app.get('/', (req, res) => {
res.send('<h1>Hello Express!</h1>');
});
// 启动服务器
app.listen(PORT, () => {
console.log(`服务器运行在 http://localhost:${PORT}`);
});
路由详解
// routes/post.js
const express = require('express');
const router = express.Router();
// 获取文章列表
router.get('/', async (req, res) => {
// query 参数: /posts?page=1&limit=10
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const skip = (page - 1) * limit;
// 模拟数据库查询
const posts = await getPosts({ skip, limit });
const total = await getTotalPosts();
res.json({
data: posts,
pagination: {
page,
limit,
total,
pages: Math.ceil(total / limit)
}
});
});
// 获取单篇文章 - 路由参数
router.get('/:id', async (req, res) => {
const { id } = req.params; // URL 参数
const post = await getPostById(id);
if (!post) {
// 返回 404 JSON
return res.status(404).json({ error: '文章不存在' });
}
res.json(post);
});
// 创建文章 - POST 请求
router.post('/', async (req, res) => {
const { title, content, authorId } = req.body; // JSON 请求体
// 数据验证
if (!title || !content) {
return res.status(400).json({ error: '标题和内容不能为空' });
}
const newPost = await createPost({ title, content, authorId });
// 201 Created
res.status(201).json(newPost);
});
// 更新文章 - PUT(完整更新)或 PATCH(部分更新)
router.put('/:id', async (req, res) => {
const { id } = req.params;
const updates = req.body;
const updated = await updatePost(id, updates);
if (!updated) {
return res.status(404).json({ error: '文章不存在' });
}
res.json(updated);
});
// 删除文章
router.delete('/:id', async (req, res) => {
const { id } = req.params;
await deletePost(id);
res.status(204).send(); // 204 No Content
});
module.exports = router;
中间件与错误处理
// middleware/auth.js
// JWT 认证中间件示例
const jwt = require('jsonwebtoken');
function authenticate(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: '未提供认证令牌' });
}
const token = authHeader.split(' ')[1];
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded; // 将用户信息挂载到 req 上
next(); // 验证通过,继续处理
} catch (err) {
return res.status(403).json({ error: '令牌无效或已过期' });
}
}
// 错误处理中间件(4个参数,Express 识别为错误处理器)
function errorHandler(err, req, res, next) {
console.error('服务器错误:', err);
// Mongoose 验证错误
if (err.name === 'ValidationError') {
return res.status(400).json({
error: '数据验证失败',
details: Object.values(err.errors).map(e => e.message)
});
}
// 其他错误
res.status(err.status || 500).json({
error: process.env.NODE_ENV === 'production'
? '服务器内部错误'
: err.message
});
}
module.exports = { authenticate, errorHandler };
完整项目结构示例
// server.js - 整合所有模块
const express = require('express');
const postRoutes = require('./routes/post');
const { authenticate, errorHandler } = require('./middleware/auth');
const app = express();
// 中间件
app.use(express.json());
app.use(express.static('public'));
// API 路由(/api/v1 前缀便于版本管理)
app.use('/api/v1/posts', postRoutes);
// 认证保护示例路由
app.get('/api/v1/profile', authenticate, (req, res) => {
res.json({ user: req.user });
});
// 404 处理
app.use((req, res) => {
res.status(404).json({ error: '接口不存在' });
});
// 错误处理器(必须放在最后)
app.use(errorHandler);
const PORT = process.env.PORT || 3000;
app.listen(Pelcome to, () => {
console.log(`Express 服务启动: http://localhost:${PORT}`);
});
常见问题
Q1: Express 的中间件执行顺序是怎样的?
中间件按照 app.use() 的注册顺序依次执行。每个中间件通过 next() 将控制权传递给下一个中间件。如果某个中间件没有调用 next(),请求就会挂起,不再继续。需要特别注意的是:错误处理中间件必须有 4 个参数 (err, req, res, next),且放在所有路由之后。
Q2: 如何防止 SQL 注入和 XSS 攻击?
Express 本身不处理这些问题,需要引入安全中间件:
- <code>express-mongo-sanitize</code> — 防止 NoSQL 注入
- <code>helmet</code> — 设置安全的 HTTP 头(防止 XSS、点击劫持等)
- <code>express-rate-limit</code> — 防止暴力破解和 DDoS
- 对用户输入始终做验证(<code>joi</code>/<code>zod</code>)和转义
Q3: Express 和 Koa 有什么区别?
Express 是回调风格的异步编程,使用传统的 req、res 对象;Koa 使用 async/await + Context 对象(ctx),更轻量,没有内置路由(需单独安装 @koa/router),洋葱模型中间件执行方式更优雅。Express 更成熟、生态更丰富;Koa 更现代、代码更简洁。
延伸阅读
- <a href="https://expressjs.com/zh-cn/">Express 官方文档</a>
- <a href="https://expressjs.com/zh-cn/advanced/best-practice-security.html">Express 最佳实践</a>
- <a href="https://restfulapi.cn/">RESTful API 设计规范</a>