中间件模型
req → middleware1 → middleware2 → handler → res
↓ ↓
next() next()
每个中间件接 (req, res, next),可以:
- 改 req / res
- 调
next()交给下一个 - 直接响应(不调 next)
- 调
next(err)跳到错误处理
基础
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next(); // ★ 别忘了
});
app.get('/', (req, res) => res.send('home'));
中间件能干什么
// 1. 日志
app.use((req, res, next) => {
console.log(`[${new Date()}] ${req.method} ${req.url}`);
next();
});
// 2. 计时
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
console.log(`${req.url} took ${Date.now() - start}ms`);
});
next();
});
// 3. 鉴权
function auth(req, res, next) {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) return res.status(401).send('Missing token');
try {
req.user = jwt.verify(token, SECRET);
next();
} catch {
res.status(401).send('Invalid token');
}
}
app.get('/me', auth, (req, res) => res.json(req.user));
// 4. CORS
app.use((req, res, next) => {
res.set('Access-Control-Allow-Origin', '*');
res.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
if (req.method === 'OPTIONS') return res.sendStatus(204);
next();
});
// 5. 限流(生产用 express-rate-limit)
import rateLimit from 'express-rate-limit';
app.use('/api', rateLimit({ windowMs: 60 * 1000, max: 100 }));
路径前缀
app.use('/api', logRequests); // 只对 /api/* 生效
app.use('/admin', requireAuth, adminRouter);
几个必装包
| 包 | 作用 |
|---|---|
morgan |
请求日志(HTTP combined / dev 格式) |
helmet |
加一组安全 HTTP header |
cors |
CORS 跨域 |
compression |
gzip 响应 |
express-rate-limit |
限流 |
cookie-parser |
解析 cookie |
express-session |
session |
multer |
上传文件 |
import morgan from 'morgan';
import helmet from 'helmet';
import cors from 'cors';
import compression from 'compression';
app.use(helmet()); // 安全 header
app.use(cors()); // 跨域
app.use(compression()); // gzip
app.use(morgan('dev')); // 日志
app.use(express.json());
错误处理中间件
4 个参数的中间件是错误处理器:
app.use((err, req, res, next) => {
// ^^^ 4 个参数
console.error(err);
res.status(err.status || 500).json({ error: err.message });
});
放在所有路由和中间件之后(最后)。
自定义错误类
class HttpError extends Error {
constructor(status, message) {
super(message);
this.status = status;
}
}
// 用
app.get('/users/:id', async (req, res, next) => {
const user = await db.users.find(req.params.id);
if (!user) throw new HttpError(404, 'User not found');
res.json(user);
});
// 全局处理
app.use((err, req, res, next) => {
if (err instanceof HttpError) {
return res.status(err.status).json({ error: err.message });
}
console.error(err);
res.status(500).json({ error: 'Internal error' });
});
404 兜底
// 放在所有路由之后
app.use((req, res) => {
res.status(404).send('Not Found');
});
一份生产级骨架
import express from 'express';
import morgan from 'morgan';
import helmet from 'helmet';
import cors from 'cors';
import compression from 'compression';
import rateLimit from 'express-rate-limit';
const app = express();
app.set('trust proxy', 1);
// 通用中间件
app.use(helmet());
app.use(cors({ origin: process.env.CORS_ORIGIN || '*' }));
app.use(compression());
app.use(morgan(process.env.NODE_ENV === 'production' ? 'combined' : 'dev'));
app.use(express.json({ limit: '1mb' }));
// 限流
app.use('/api', rateLimit({ windowMs: 60_000, max: 100 }));
// 健康检查
app.get('/health', (req, res) => res.json({ ok: true }));
// 业务路由
app.use('/api/users', usersRouter);
app.use('/api/posts', postsRouter);
// 404
app.use((req, res) => res.status(404).json({ error: 'Not Found' }));
// 错误处理(最后)
app.use((err, req, res, next) => {
console.error(err);
const status = err.status || 500;
res.status(status).json({
error: status >= 500 ? 'Internal error' : err.message,
});
});
app.listen(process.env.PORT || 3000);
坑
- 中间件顺序很重要——先注册先跑
- 调了
next()之后还res.send()会报错(headers already sent) - 错误处理中间件必须 4 个参数——少一个就不被识别为错误处理器
- 异步中间件忘 await/return 可能让请求挂起
下一篇:Fastify。