返回博客
技术 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())
morganHTTP 请求日志app.use(morgan('combined'))
express-rate-limit限流app.use(rateLimit(...))
compressionGzip 压缩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 对比

特性ExpressKoaFastify
中间件模型回调线性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 的社区资源和成熟度仍使其成为大多数项目的可靠选择。