3.1 中间件是什么
Express 的核心就是中间件(Middleware)。
一个请求过来,会依次经过一个个中间件,每个中间件可以:
- 读取/修改 request 对象
- 读取/修改 response 对象
- 结束响应(直接返回)
- 调用
next()把控制权交给下一个中间件
bash
请求 → 中间件1 → 中间件2 → 中间件3 → 路由处理 → 响应
↓
出错 → 错误处理中间件
中间件的类型
- 应用级中间件 :用
app.use()挂载,对所有请求生效 - 路由级中间件 :绑定在
Router上,只对该路由生效 - 错误处理中间件 :有 4 个参数
(error, req, res, next),专门处理错误 - 内置中间件 :Express 自带的,比如
express.json() - 第三方中间件 :比如
cors、morgan等
3.2 App 的组织方式
不要把所有东西都塞在 server.ts 里。我们分成两个文件:
app.ts--- 创建 Express App,注册中间件和路由server.ts--- 启动服务,监听端口
这样做的好处是测试方便 :测试时直接 import createApp() 拿到 app 实例,不需要启动服务器。
3.3 完整的 App 创建
src/app.ts:
typescript
import express from 'express';
import { errorHandler } from './middlewares/error-handler.js';
import { notFound } from './middlewares/not-found.js';
import { requestLogger } from './middlewares/logger.js';
import { createRouter } from './routers/index.js';
export function createApp() {
const app = express();
// ===== 安全相关 =====
app.disable('x-powered-by'); // 隐藏 Express 标识,防止被针对性攻击
// ===== 全局中间件(按顺序执行)=====
// 1. JSON 请求体解析
app.use(express.json({ limit: '10mb' }));
// 2. 请求日志(只记录 API 请求)
app.use('/api', requestLogger);
// ===== 路由 =====
app.use('/api', createRouter());
// ===== 错误处理(注意顺序!必须在路由之后)=====
app.use(notFound); // 404
app.use(errorHandler); // 全局错误捕获
return app;
}
中间件的顺序很重要
Express 按代码书写顺序执行中间件。常见的坑:
- ❌ 把
express.json()放在路由之后 → 拿不到请求体 - ❌ 把错误处理中间件放在路由之前 → 捕获不到路由里的错误
- ❌ 把鉴权中间件放在路由之后 → 等于没鉴权
正确顺序:
3.4 服务启动入口
src/server.ts:
typescript
import { createApp } from './app.js';
const PORT = Number(process.env.PORT) || 3000;
const HOST = process.env.HOST || '0.0.0.0';
const app = createApp();
const server = app.listen(PORT, HOST, () => {
console.log(`Server running at http://${HOST}:${PORT}`);
});
// 端口占用处理
server.once('error', (error: NodeJS.ErrnoException) => {
if (error.code === 'EADDRINUSE') {
console.error(`端口 ${PORT} 已被占用`);
} else {
console.error(error);
}
process.exitCode = 1;
});
// 优雅退出:收到关闭信号时,先关闭服务器再退出
for (const signal of ['SIGINT', 'SIGTERM'] as const) {
process.once(signal, () => {
console.log('正在关闭服务器...');
server.close((error) => {
if (error) {
console.error(error);
process.exitCode = 1;
} else {
console.log('服务器已关闭');
}
});
});
}
为什么要优雅退出
- 正在处理的请求能正常完成
- 可以关闭数据库连接、清理资源
- 不会突然中断导致数据不一致
3.5 环境配置
不要把端口号、数据库地址这些硬编码在代码里,用环境变量管理。
src/config/server.ts:
typescript
export const SERVER_PORT = Number(process.env.SERVER_PORT) || 3000;
export const SERVER_HOST = process.env.SERVER_HOST || '0.0.0.0';
export const SERVER_NAME = process.env.SERVER_NAME || 'my-backend';
根目录创建 .env:
env
SERVER_PORT=3000
SERVER_HOST=0.0.0.0
SERVER_NAME=my-backend
如果用 .env 文件,需要安装 dotenv 并在入口文件最顶部引入:
bash
npm install dotenv
src/server.ts 最顶部加:
typescript
import 'dotenv/config';
3.6 常用的中间件清单
| 中间件 | 作用 | 安装 |
|---|---|---|
express.json() |
解析 JSON 请求体 | Express 内置 |
express.urlencoded() |
解析表单请求体 | Express 内置 |
express.static() |
托管静态文件 | Express 内置 |
cors |
跨域 | npm install cors |
helmet |
安全头 | npm install helmet |
morgan |
访问日志 | npm install morgan |
cookie-parser |
解析 Cookie | npm install cookie-parser |
express-rate-limit |
限流 | npm install express-rate-limit |
3.7 小结
- Express 的核心是中间件,顺序很重要
- 把 App 创建和服务启动分成两个文件,方便测试
- 配置用环境变量,不要硬编码
- 错误处理中间件要放在路由之后