在 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.static、express.json、express.urlencoded等。 - 第三方中间件:如
cors、morgan、helmet等。
下面是一个典型的中间件栈:
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.body 是 undefined。使用 express.json() 和 express.urlencoded() 后,req.body 会被填充为解析后的对象。这些内置中间件实际上是对第三方 body-parser 的封装。
12.1.3 路由系统
Express 的路由定义了应用程序如何响应客户端对特定端点(URI)的请求。路由定义的结构如下:
app.METHOD(path, [callback...], callback)
其中 METHOD 是 HTTP 请求方法(get、post、put、delete 等),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 标准格式
常用的预定义格式有 dev、combined、common、short。也可以自定义日志格式。生产环境经常将日志输出到文件或日志收集系统,可与 winston 等日志库配合。
安全相关
helmet 通过设置一系列 HTTP 安全头,帮助应用防御一些常见的 Web 漏洞。
const helmet = require('helmet');
app.use(helmet());
它会启用 X-DNS-Prefetch-Control、X-Frame-Options、X-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.js、routes/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)的基础。