如何用TypeScript编写类Express中间件的Node.js函数?
纯Node.js + TypeScript 实现类Express中间件的最佳实践
一、先统一核心类型定义(TS强约束基础)
脱离Express的类型依赖,基于Node原生HTTP对象定义通用上下文和中间件类型,确保跨环境兼容性:
import { IncomingMessage, ServerResponse } from 'http'; // 定义Next函数类型,支持同步/异步流转 type Next = () => Promise<void> | void; // 通用上下文:基于Node原生对象,预留扩展字段 interface Context { req: IncomingMessage; res: ServerResponse; // 允许添加自定义属性(如用户信息、请求ID等) [key: string]: any; } // 标准中间件类型:对齐Express的(req,res,next)结构,但使用自定义Context type Middleware = (ctx: Context, next: Next) => Promise<void> | void;
二、装饰器方案的落地规范
你的装饰器思路完全可行,装饰器可用于封装通用逻辑(如错误捕获、日志、权限校验),让业务中间件专注核心逻辑。以下是两种常见装饰器的实现规范:
1. 中间件增强装饰器(包装已有中间件)
比如实现错误捕获装饰器,自动处理中间件抛出的异常,避免重复编写try/catch:
// 错误捕获装饰器 const catchErrors = (middleware: Middleware): Middleware => { return async (ctx, next) => { try { await middleware(ctx, next); } catch (err) { // 统一错误处理逻辑:返回500响应、记录日志 ctx.res.writeHead(500, { 'Content-Type': 'application/json' }); ctx.res.end(JSON.stringify({ error: (err as Error).message })); } }; }; // 使用示例:给业务中间件添加错误捕获能力 const userMiddleware: Middleware = async (ctx, next) => { // 模拟业务报错 throw new Error('获取用户信息失败'); await next(); }; // 增强后的中间件 const enhancedUserMiddleware = catchErrors(userMiddleware);
2. 类式中间件的装饰器(用类组织中间件逻辑)
如果偏好用类管理中间件(类似NestJS风格),可通过方法装饰器标记中间件方法,再转为标准Middleware类型:
// 装饰器:标记类方法为中间件 const Middleware = () => { return (target: any, propertyKey: string) => { // 把方法挂载到类的静态属性,方便后续收集 target.__middlewares = target.__middlewares || []; target.__middlewares.push(target[propertyKey]); }; }; // 定义中间件类 class AuthMiddleware { @Middleware() async checkToken(ctx: Context, next: Next) { const token = ctx.req.headers['authorization']; if (!token) { ctx.res.writeHead(401, { 'Content-Type': 'application/json' }); ctx.res.end(JSON.stringify({ error: '未授权' })); return; } // 验证token逻辑... ctx.user = { id: 123 }; // 将用户信息挂载到上下文 await next(); } } // 将类中的中间件方法转为标准Middleware数组 const authMiddlewareList = AuthMiddleware.__middlewares as Middleware[];
三、中间件执行器的实现(核心调度逻辑)
要让中间件像Express一样按顺序执行,需实现一个轻量调度器,这是脱离Express后的核心部分,确保跨环境可用:
// 中间件执行器:按顺序执行中间件队列 const runMiddlewares = async (ctx: Context, middlewares: Middleware[]) => { let index = 0; // 定义next函数,控制中间件流转 const next: Next = async () => { if (index >= middlewares.length) return; const currentMiddleware = middlewares[index++]; await currentMiddleware(ctx, next); }; await next(); }; // 使用示例:创建Node HTTP服务器,绑定中间件 import { createServer } from 'http'; const server = createServer(async (req, res) => { const ctx: Context = { req, res }; // 中间件队列 const middlewares = [ catchErrors(async (ctx, next) => { console.log('第一个中间件:日志记录'); await next(); }), ...authMiddlewareList, async (ctx, next) => { // 业务处理逻辑 ctx.res.writeHead(200, { 'Content-Type': 'application/json' }); ctx.res.end(JSON.stringify({ message: '请求成功', user: ctx.user })); await next(); } ]; await runMiddlewares(ctx, middlewares); }); server.listen(3000, () => console.log('服务器运行在3000端口'));
四、跨环境适配的注意事项
- 避免依赖特定框架API:全程使用Node原生
http模块的IncomingMessage和ServerResponse,如需解析请求体,可使用querystring、raw-body等轻量库,避免引入Express冗余依赖。 - AWS环境优化:在AWS Lambda中无需创建HTTP服务器,只需将中间件执行器适配Lambda事件模型:
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; import { IncomingMessage, ServerResponse } from 'http'; import { Readable } from 'stream'; // 将Lambda事件转为Node原生IncomingMessage const eventToRequest = (event: APIGatewayProxyEvent): IncomingMessage => { const req = new IncomingMessage(Readable.from(Buffer.from(event.body || ''))); req.method = event.httpMethod; req.url = event.path; req.headers = event.headers as any; return req; }; // 将ServerResponse转为Lambda返回结果 const responseToResult = (res: ServerResponse): APIGatewayProxyResult => { // 需扩展ServerResponse的getBody方法收集响应内容,或通过回调实现 return { statusCode: res.statusCode || 200, headers: res.getHeaders() as any, body: res.getBody() || '' }; }; // Lambda处理函数 export const handler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => { const req = eventToRequest(event); const res = new ServerResponse(req); const ctx: Context = { req, res }; await runMiddlewares(ctx, middlewares); return responseToResult(res); }; - 保持中间件单一职责:每个中间件只做一件事(如日志、权限、请求体解析),便于复用和调试。
- 强制异步支持:所有中间件需支持异步(返回Promise或使用async/await),执行器需正确处理异步流转,避免回调地狱。
五、装饰器思路的验证
你的装饰器思路是合理且高效的,核心优势包括:
- 封装通用逻辑,减少重复代码;
- 保持业务中间件的简洁性;
- 符合TS类型约束,可读性更强;
- 完全脱离Express依赖,资源占用更低,适配AWS等无服务器环境。
内容的提问来源于stack exchange,提问作者Irfandy J.
相关产品推荐
相关产品推荐

