tRPC多中间件调用实现及框架选型咨询
问题解答
一、在tRPC中实现类似Express多中间件的逻辑
tRPC没有Express那种直接挂载多个中间件到路由的语法,但可以通过两种方式实现相同的「多步骤依次执行、传递上下文」逻辑:
方式1:使用tRPC的Middleware链式调用
tRPC的Procedure支持通过.use()方法添加多个中间件,每个中间件可以修改上下文(ctx),并传递给后续的中间件或最终的handler。你可以把Express里的connectDB、createExtension等逻辑封装成tRPC中间件:
import { middleware, TRPCError } from "@trpc/server"; import { Pool } from "pg"; // 封装数据库连接逻辑为tRPC中间件 const connectDB = middleware(async ({ input, ctx, next }) => { const pool = new Pool({ connectionString: input.URI }); try { // 替换回调为异步await,保证连接成功再往下走 await pool.query('SELECT NOW()'); console.log('Connection to "postgres" database successful'); // 将连接池存入ctx,供后续步骤使用 ctx.dbConnection = pool; // 调用next传递更新后的ctx return next({ ctx }); } catch (error) { console.error('Error executing query:', error); throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: '数据库连接失败' }); } }); // 封装创建扩展的中间件 const createExtension = middleware(async ({ ctx, next }) => { if (!ctx.dbConnection) { throw new TRPCError({ code: 'BAD_REQUEST', message: '未建立数据库连接' }); } await ctx.dbConnection.query('CREATE EXTENSION IF NOT EXISTS pg_stat_statements'); // 替换为你的扩展语句 return next(); });
然后在路由中链式使用这些中间件:
export const exampleRouter = createTRPCRouter({ // 对应Express的/uri路由 validateDB: protectedProcedure .input(z.object({ URI: z.string() })) .use(connectDB) .use(createExtension) .query(async ({ ctx }) => { // 调用数据库统计方法,将结果直接返回(替代Express的res.locals) const stats = await databaseController.dbStats(ctx.dbConnection); return stats; }), // 对应Express的/queryMetrics路由 getQueryMetrics: protectedProcedure .input(z.object({ URI: z.string() })) .use(connectDB) .use(createExtension) .query(async ({ ctx }) => { // 用Promise.all并行执行查询提升效率 const [queryTimes, numOfRows, topCalls, dbStats, cacheHitRatio, statActivity] = await Promise.all([ databaseController.queryTimes(ctx.dbConnection), databaseController.numOfRows(ctx.dbConnection), databaseController.topCalls(ctx.dbConnection), databaseController.dbStats(ctx.dbConnection), databaseController.cacheHitRatio(ctx.dbConnection), databaseController.statActivity(ctx.dbConnection) ]); return { queryTimes, numOfRows, topCalls, dbStats, cacheHitRatio, statActivity }; }), // 保留你的原有路由 hello: publicProcedure .input(z.object({ text: z.string() })) .query(({ input }) => { return { greeting: `Hello ${input.text}`, }; }), getAll: publicProcedure.query(async ({ ctx }) => { console.log(ctx.session) const users = await ctx.prisma.user.findMany(); const names = users.map(user => user.name); return names; }), getSecretMessage: protectedProcedure.query(() => { return "you can now see this secret message!"; }), });
方式2:在Handler中直接顺序调用函数
如果项目周期紧张,不想花时间封装中间件,更直接的方式是在Procedure的handler里按顺序调用所有需要的方法,把结果收集后返回:
export const exampleRouter = createTRPCRouter({ getQueryMetrics: protectedProcedure .input(z.object({ URI: z.string() })) .query(async ({ input }) => { // 步骤1:建立数据库连接 const pool = new Pool({ connectionString: input.URI }); try { await pool.query('SELECT NOW()'); // 步骤2:创建扩展 await pool.query('CREATE EXTENSION IF NOT EXISTS pg_stat_statements'); // 步骤3:依次调用所有指标方法 const queryTimes = await databaseController.queryTimes(pool); const numOfRows = await databaseController.numOfRows(pool); const topCalls = await databaseController.topCalls(pool); // ...其他方法 // 收集结果返回 return { queryTimes, numOfRows, topCalls /* ...其他结果 */ }; } catch (error) { console.error(error); throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: '查询失败' }); } finally { await pool.end(); // 记得关闭连接 } }), // 其他路由... });
这种方式不需要学习tRPC的middleware机制,上手更快,适合短周期项目快速落地。
二、技术选型:继续用tRPC还是切换回Express?
不建议切换的情况
如果你们已经基于T3栈搭建了基础框架,且已经实现了部分核心功能:
- T3栈是为tRPC、Next.js、Prisma等工具量身设计的,切换回Express需要重新适配认证、数据库连接、前端API调用等逻辑,反而会浪费更多时间
- 你需要的多中间件逻辑已经可以通过上述两种方式实现,学习成本极低,1-2天就能掌握核心用法
可以考虑切换的情况
如果项目还处于非常早期,且团队全员对Express的熟悉程度远高于tRPC:
- Express的路由中间件模式你们已经熟练掌握,不用额外学习新语法,能更快完成开发
- 但要注意T3栈的其他组件(比如Prisma、Next.js)和Express的适配问题,可能需要额外的配置工作
最终建议
因为项目周期仅有几周,且已经选定T3栈,优先建议继续使用tRPC。可以先采用「handler内顺序调用函数」的方式快速实现功能,后续如果有时间再优化成middleware模式,这样既能保证开发效率,又不用推翻现有工作。
内容的提问来源于stack exchange,提问作者Alan B.
相关产品推荐
相关产品推荐

