人人都会AI编程

12.1 Express

更新时间:2026-07-10

在 Node.js 生态中,Express 是历史最悠久、社区最庞大的 Web 服务框架。它本身极为精简,只提供最核心的 HTTP 服务能力:路由、中间件、请求/响应增强。正是这种“微内核”设计,使得 Express 非常灵活,通过第三方中间件几乎可以扩展出任何 Web 应用所需的能力。

12.1.1 安装与第一个服务

Express 作为一个 npm 包,安装很简单:

npm install express

一个最小化的 Express 应用只需几行代码:

const express = require('express');
const app = express();

app.get('/', (req, res) => {
  res.send('Hello World');
});

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

与 Node.js 原生 http 模块相比,Express 在路由匹配、请求解析、响应发送上都做了大量简化。res.send() 可以自动设置 Content-Type,字符串输出 text/html,对象则转成 JSON,开发者无需手动设置头信息和编码。

12.1.2 中间件核心机制

中间件(Middleware)是 Express 的灵魂。一个中间件就是一个函数,它能够访问请求对象(req)、响应对象(res)以及下一个中间件函数(next)。中间件可以执行任何代码、修改请求和响应、结束请求-响应循环,或者把控制权交给堆栈中的下一个中间件。

app.use((req, res, next) => {
  console.log(`${req.method} ${req.url}`);
  next(); // 必须调用 next 才能流转到后续中间件或路由
});

中间件的执行顺序与注册顺序完全一致。Express 内部会维护一个中间件数组,当请求到达时,从上到下依次调用。如果某个中间件没有调用 next(),请求就会被挂起,后续中间件和路由都不会被执行。

中间件分类

  • 应用级中间件:通过 app.use()app.METHOD() 绑定到 app 实例上。
  • 路由级中间件:通过 express.Router() 创建路由实例,然后使用 router.use() 绑定。
  • 错误处理中间件:签名有四个参数 (err, req, res, next),专门处理错误。
  • 内置中间件express.staticexpress.jsonexpress.urlencoded 等。
  • 第三方中间件:如 corsmorganhelmet 等。

下面是一个典型的中间件栈:

const express = require('express');
const app = express();

// 内置中间件:解析 JSON 请求体
app.use(express.json());
// 内置中间件:解析 URL-encoded 表单数据
app.use(express.urlencoded({ extended: true }));
// 第三方中间件:日志记录
const morgan = require('morgan');
app.use(morgan('dev'));
// 自定义中间件:鉴权示例
app.use((req, res, next) => {
  if (req.path === '/public') return next();
  if (!req.headers.authorization) {
    return res.status(401).send('Unauthorized');
  }
  next();
});
// 路由
app.get('/data', (req, res) => {
  res.json({ message: 'protected data' });
});
// 错误处理中间件(最后注册)
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send('Something broke!');
});

app.listen(3000);

在 Express 中,请求体默认不会被解析,req.bodyundefined。使用 express.json()express.urlencoded() 后,req.body 会被填充为解析后的对象。这些内置中间件实际上是对第三方 body-parser 的封装。

12.1.3 路由系统

Express 的路由定义了应用程序如何响应客户端对特定端点(URI)的请求。路由定义的结构如下:

app.METHOD(path, [callback...], callback)

其中 METHOD 是 HTTP 请求方法(getpostputdelete 等),path 是服务器上的路径,callback 是当路由匹配时要执行的函数。

基本路由

app.get('/users', (req, res) => {
  res.send('user list');
});

app.post('/users', (req, res) => {
  res.send('create user');
});

app.put('/users/:id', (req, res) => {
  res.send(`update user ${req.params.id}`);
});

:id 是路由参数,可以通过 req.params.id 获取。路由参数也是重要的数据来源,应当在使用前进行校验。

路由句柄的组合

一个路由可以设置多个回调函数,这对于拆分中间件逻辑或验证很有用:

app.get('/secure',
  (req, res, next) => {
    console.log('验证 Token');
    next();
  },
  (req, res) => {
    res.send('安全数据');
  }
);

也可以把一组相同前缀的路由拆分为独立模块。创建 userRoutes.js

const express = require('express');
const router = express.Router();

router.get('/', (req, res) => {
  res.send('list');
});
router.post('/', (req, res) => {
  res.send('create');
});
router.get('/:id', (req, res) => {
  res.send(`user ${req.params.id}`);
});

module.exports = router;

主文件挂载:

const users = require('./userRoutes');
app.use('/users', users);

这样,所有 /users 开头的请求都会被 userRoutes 处理,方便代码组织和功能拆分。

路由匹配与 404 处理

Express 按顺序尝试匹配定义的路由,如果一个都没有命中,就会跳过所有路由中间件。通常会在所有路由之后添加一个通用的 404 处理:

app.use((req, res) => {
  res.status(404).send('Not Found');
});

由于没有路由给他处理,Express 就会走进这个中间件,因为它没有路径限制且注册在最后。

12.1.4 错误处理

Express 内置了一个默认的错误处理器,它会将错误信息输出到控制台,并在生产环境返回 500 状态码。但实际项目中通常需要自定义错误处理逻辑。

错误处理中间件需要接受四个参数:(err, req, res, next)。Express 会根据参数个数区分普通中间件和错误处理中间件。

// 触发错误的路由
app.get('/error', (req, res, next) => {
  const err = new Error('Something went wrong');
  err.status = 400;
  next(err); // 将错误传给错误处理中间件
});

// 自定义错误处理
app.use((err, req, res, next) => {
  const status = err.status || 500;
  res.status(status).json({
    error: {
      message: err.message,
      status: status
    }
  });
});

如果传递了一个错误给 next()(例如 next(err)),Express 会跳过所有剩余的普通中间件,直接跳转到错误处理中间件。如果错误处理中间件也调用了 next(err),并且没有其他错误处理中间件,该错误会被默认处理器捕获并打印。

异步错误的处理

Express 4 及更早版本不能自动捕获 Promise 的拒绝,如果异步路由中抛出错误,需要显式 catch 并传给 next。Express 5(目前仍为 alpha 状态)将会原生支持异步错误的自动捕获。在实践中,通常会封装一个异步包装器:

const asyncHandler = fn => (req, res, next) =>
  Promise.resolve(fn(req, res, next)).catch(next);

app.get('/async', asyncHandler(async (req, res) => {
  const data = await someAsyncOp();
  res.json(data);
}));

这样可以避免每个路由都写 try/catch

12.1.5 常用中间件

Express 生态中有一批久经考验的中间件,几乎每个项目都会用到。

静态文件服务

express.static 是唯一的内置静态文件中间件,它基于 serve-static,可以高效地提供静态资源。

app.use(express.static('public'));

这样就可以直接通过浏览器访问 public 文件夹下的文件,例如 http://localhost:3000/image.png。通常还会配置缓存、自定义路径前缀:

app.use('/static', express.static('public', {
  maxAge: '1d',
  setHeaders: (res, path) => {
    if (path.endsWith('.html')) {
      res.setHeader('Cache-Control', 'no-cache');
    }
  }
}));

请求体解析

Express 从 4.16 版本开始内置了基于 body-parser 的解析中间件:

  • express.json():解析 Content-Type: application/json 的请求体。
  • express.urlencoded({ extended: true }):解析 URL 编码的表单数据(extended 决定使用 querystring 还是 qs 库)。
  • express.raw():获取原始 Buffer 数据。
  • express.text():解析文本请求体。
app.use(express.json({ limit: '1mb' }));
app.use(express.urlencoded({ extended: true }));

跨域处理

CORS(Cross-Origin Resource Sharing)是浏览器安全策略,后端必须显式允许跨域请求。使用 cors 中间件可以简单开启:

npm install cors
const cors = require('cors');

// 允许所有源访问
app.use(cors());

// 限制特定源
app.use(cors({
  origin: 'https://example.com',
  methods: ['GET', 'POST'],
  credentials: true
}));

日志记录

morgan 是 Express 环境下最流行的 HTTP 请求日志中间件,它可以按多种格式输出日志。

npm install morgan
const morgan = require('morgan');
app.use(morgan('combined')); // Apache 标准格式

常用的预定义格式有 devcombinedcommonshort。也可以自定义日志格式。生产环境经常将日志输出到文件或日志收集系统,可与 winston 等日志库配合。

安全相关

helmet 通过设置一系列 HTTP 安全头,帮助应用防御一些常见的 Web 漏洞。

const helmet = require('helmet');
app.use(helmet());

它会启用 X-DNS-Prefetch-ControlX-Frame-OptionsX-Powered-By 隐藏、HSTS 等头信息,大幅提高安全性。

其他常用中间件

  • compression:开启 Gzip/Brotli 压缩。
  • cookie-parser:解析 Cookie 头,并将结果填入 req.cookies
  • express-session:服务端会话管理。
  • csrf:防范 CSRF 攻击。

12.1.6 应用组织与最佳实践

随着项目规模增长,单个 app.js 会变得臃肿。Express 本身不强制任何项目结构,以下约定被社区广泛接受:

  • 使用 express.Router 拆分功能模块(如 routes/users.jsroutes/products.js)。
  • 将中间件提取为独立模块(如 middleware/auth.js)。
  • 将业务逻辑放入控制器文件(controllers/userController.js),路由文件只做分发。
  • 利用 app.set 保存配置(如 app.set('port', process.env.PORT)),并使用 app.get 获取。
  • 错误处理统一由一个集中的错误处理中间件完成。

示例项目结构:

├── app.js
├── routes
│   ├── index.js
│   └── users.js
├── controllers
│   └── userController.js
├── middleware
│   └── auth.js
├── models
│   └── user.js
└── public

12.1.7 Express 的现状与定位

Express 自 2010 年发布以来,至今仍是 Node.js Web 框架的基准。它的核心 API 非常稳定,大量生产环境的应用运行在 Express 之上。虽然新的框架(如 Koa、Fastify、NestJS)在性能或架构模式上有所创新,但 Express 凭借庞大的中间件生态和极低的学习曲线,仍然是很多项目的首选。

对于中小型项目、API 原型开发,或者对性能没有极致要求的场景,Express 依旧是一个非常实用且可靠的选择。掌握 Express 的中间件机制、路由组织和错误处理,也是理解其他更复杂框架(如 NestJS)的基础。