You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 16:23:19