返回博客
技术 2025年3月17日 9 分钟阅读 · 1910 字
Express.js 框架入门与实战
从路由到中间件,用 Express 构建生产级 RESTful API 服务
#Node.js
#Express
#RESTful API
#后端
本文由 AI 辅助生成,经人工审核发布
Express.js 是 Node.js 生态中最经典的 Web 框架,自 2010 年发布以来一直是构建后端服务的首选方案。它的核心哲学是”小而美”——框架本身只提供路由和中间件机制,其余功能通过第三方中间件扩展。本文将系统讲解 Express 的核心概念,并构建一个生产级 RESTful API 服务。
一、Express 核心概念
Express 的本质是一个极简的 HTTP 请求处理管道。每个请求进来后,会经过一串中间件函数,最终到达路由处理器。
const express = require('express');
const app = express();
// 中间件
app.use(express.json());
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next();
});
// 路由
app.get('/', (req, res) => {
res.json({ message: 'Hello Express' });
});
app.listen(3000, () => console.log('服务启动: http://localhost:3000'));
核心对象关系:
| 对象 | 作用 | 典型方法 |
|---|---|---|
app | 应用实例 | app.use(), app.get(), app.listen() |
req | 请求对象 | req.params, req.query, req.body |
res | 响应对象 | res.json(), res.send(), res.status() |
router | 路由模块 | router.get(), router.post() |
next | 下一个中间件 | next(), next(err) |
二、路由定义与参数
2.1 基本路由
Express 支持 RESTful 风格的路由定义:
// 获取所有用户
app.get('/api/users', (req, res) => {
res.json(users);
});
// 获取单个用户
app.get('/api/users/:id', (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) return res.status(404).json({ error: '用户不存在' });
res.json(user);
});
// 创建用户
app.post('/api/users', (req, res) => {
const user = { id: Date.now(), ...req.body };
users.push(user);
res.status(201).json(user);
});
// 更新用户
app.put('/api/users/:id', (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) return res.status(404).json({ error: '用户不存在' });
Object.assign(user, req.body);
res.json(user);
});
// 删除用户
app.delete('/api/users/:id', (req, res) => {
const index = users.findIndex(u => u.id === parseInt(req.params.id));
if (index === -1) return res.status(404).json({ error: '用户不存在' });
users.splice(index, 1);
res.status(204).send();
});
2.2 路由参数与查询
// 路径参数
app.get('/users/:userId/posts/:postId', (req, res) => {
res.json({
userId: req.params.userId,
postId: req.params.postId
});
});
// 查询字符串: /search?q=nodejs&page=2&limit=10
app.get('/search', (req, res) => {
const { q, page = 1, limit = 20 } = req.query;
res.json({ keyword: q, page: Number(page), limit: Number(limit) });
});
// 通配符路由
app.get('/files/*', (req, res) => {
res.send(`请求文件路径: ${req.params[0]}`);
});
2.3 使用 express.Router 模块化路由
// routes/users.js
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => res.json(users));
router.get('/:id', (req, res) => res.json(users[req.params.id]));
router.post('/', (req, res) => res.status(201).json({ created: true }));
module.exports = router;
// app.js
const usersRouter = require('./routes/users');
app.use('/api/users', usersRouter);
三、中间件机制
中间件是 Express 的灵魂。每个中间件是一个接收 (req, res, next) 的函数,可以选择结束响应或调用 next() 传递给下一个中间件。
3.1 应用级中间件
// 对所有请求生效
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// 日志中间件
app.use((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();
});
3.2 路由级中间件
const router = express.Router();
// 认证中间件
const auth = (req, res, next) => {
const token = req.headers.authorization;
if (!token) return res.status(401).json({ error: '未提供认证信息' });
try {
req.user = verifyToken(token);
next();
} catch {
res.status(401).json({ error: '认证失败' });
}
};
// 仅对 /api/admin 路径生效
router.use('/admin', auth);
router.get('/admin/dashboard', (req, res) => {
res.json({ message: `欢迎 ${req.user.name}` });
});
3.3 第三方常用中间件
| 中间件 | 用途 | 示例 |
|---|---|---|
helmet | 安全 HTTP 头 | app.use(helmet()) |
cors | 跨域资源共享 | app.use(cors()) |
morgan | HTTP 请求日志 | app.use(morgan('combined')) |
express-rate-limit | 限流 | app.use(rateLimit(...)) |
compression | Gzip 压缩 | app.use(compression()) |
const helmet = require('helmet');
const cors = require('cors');
const morgan = require('morgan');
const rateLimit = require('express-rate-limit');
const compression = require('compression');
// 安全中间件
app.use(helmet());
// CORS 配置
app.use(cors({
origin: ['https://example.com', 'https://app.example.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
credentials: true
}));
// 限流:每个 IP 每 15 分钟最多 100 次请求
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
message: { error: '请求过于频繁,请稍后再试' }
});
app.use('/api/', limiter);
// 压缩响应
app.use(compression());
四、错误处理中间件
错误处理中间件必须接收四个参数 (err, req, res, next),Express 通过参数数量识别它。
4.1 自定义错误处理
// 404 处理
app.use((req, res, next) => {
res.status(404).json({ error: '资源不存在', path: req.path });
});
// 全局错误处理
app.use((err, req, res, next) => {
console.error(err.stack);
if (err.name === 'ValidationError') {
return res.status(400).json({ error: err.message });
}
if (err.name === 'UnauthorizedError') {
return res.status(401).json({ error: '无效的 Token' });
}
res.status(500).json({ error: '服务器内部错误' });
});
4.2 异步错误捕获
Express 4 不会自动捕获 async 函数中的错误,需要手动处理:
// 方式一:try-catch
app.get('/api/users/:id', async (req, res, next) => {
try {
const user = await User.findById(req.params.id);
if (!user) throw new Error('用户不存在');
res.json(user);
} catch (err) {
next(err);
}
});
// 方式二:包装函数(推荐)
const asyncHandler = fn => (req, res, next) =>
Promise.resolve(fn(req, res, next)).catch(next);
app.get('/api/users/:id', asyncHandler(async (req, res) => {
const user = await User.findById(req.params.id);
if (!user) throw new Error('用户不存在');
res.json(user);
}));
五、模板引擎集成
Express 支持多种模板引擎,常用的有 EJS、Pug、Handlebars。
// 设置 EJS 模板引擎
app.set('view engine', 'ejs');
app.set('views', './views');
// 渲染模板
app.get('/profile/:name', (req, res) => {
res.render('profile', {
name: req.params.name,
title: '个人资料',
items: ['文章1', '文章2', '文章3']
});
});
EJS 模板示例:
<!-- views/profile.ejs -->
<!DOCTYPE html>
<html>
<head><title><%= title %></title></head>
<body>
<h1><%= name %> 的个人资料</h1>
<ul>
<% items.forEach(item => { %>
<li><%= item %></li>
<% }) %>
</ul>
</body>
</html>
六、静态文件服务
// 基本静态文件服务
app.use(express.static('public'));
// 指定虚拟路径前缀
app.use('/static', express.static('public'));
// 设置缓存和缓存控制
const oneDay = 86400000;
app.use('/assets', express.static('assets', {
maxAge: oneDay,
etag: true,
lastModified: true
}));
七、完整生产级 API 示例
const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const morgan = require('morgan');
const compression = require('compression');
const rateLimit = require('express-rate-limit');
const app = express();
// 中间件栈
app.use(helmet());
app.use(compression());
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
app.use(cors({ origin: process.env.CORS_ORIGIN || '*' }));
app.use(morgan(process.env.NODE_ENV === 'production' ? 'combined' : 'dev'));
app.use('/api/', rateLimit({ windowMs: 60000, max: 60 }));
// 数据存储(演示用)
let todos = [];
let nextId = 1;
// 路由
app.get('/api/todos', (req, res) => {
res.json({ data: todos, total: todos.length });
});
app.post('/api/todos', (req, res) => {
const { title } = req.body;
if (!title) return res.status(400).json({ error: '标题不能为空' });
const todo = { id: nextId++, title, completed: false, createdAt: new Date() };
todos.push(todo);
res.status(201).json(todo);
});
app.put('/api/todos/:id', (req, res) => {
const todo = todos.find(t => t.id === Number(req.params.id));
if (!todo) return res.status(404).json({ error: '待办不存在' });
Object.assign(todo, req.body);
res.json(todo);
});
app.delete('/api/todos/:id', (req, res) => {
const index = todos.findIndex(t => t.id === Number(req.params.id));
if (index === -1) return res.status(404).json({ error: '待办不存在' });
todos.splice(index, 1);
res.status(204).send();
});
// 错误处理
app.use((req, res) => res.status(404).json({ error: '资源不存在' }));
app.use((err, req, res, next) => {
console.error(err);
res.status(500).json({ error: '服务器内部错误' });
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`API 服务运行在端口 ${PORT}`));
八、与 Koa、Fastify 对比
| 特性 | Express | Koa | Fastify |
|---|---|---|---|
| 中间件模型 | 回调线性 | async 洋葱模型 | 钩子函数 |
| 异步支持 | 需包装 | 原生 async/await | 原生 async/await |
| 性能 | 基准 | 略快 | 最快(2-3 倍) |
| TypeScript | 需 @types | 需配置 | 原生支持 |
| 插件生态 | 最丰富 | 丰富 | 增长迅速 |
| 学习曲线 | 最低 | 中等 | 中等 |
// Koa 洋葱模型示例
const Koa = require('koa');
const app = new Koa();
app.use(async (ctx, next) => {
console.log('第一层 - 前');
await next();
console.log('第一层 - 后');
});
app.use(async (ctx, next) => {
console.log('第二层 - 前');
await next();
console.log('第二层 - 后');
});
app.use(async ctx => {
ctx.body = 'Hello Koa';
console.log('响应处理');
});
// 输出顺序:第一层前 → 第二层前 → 响应处理 → 第二层后 → 第一层后
总结
Express.js 以极简的设计和强大的中间件生态,依然是 Node.js 后端开发的主流选择。掌握路由定义、中间件链、错误处理三大核心机制后,配合 helmet、cors、rate-limit 等安全中间件,即可构建出生产可用的 RESTful API 服务。虽然新一代框架(如 Fastify)在性能上有优势,但 Express 的社区资源和成熟度仍使其成为大多数项目的可靠选择。