mcpField
T
trpc-router

trpc-router

github.com/lobehub开发83.6k127 次安装今天更新
代码仓库

关于 trpc-router

面向服务端开发的 tRPC 路由编写指南,涵盖过程定义、通过中间件注入模型、输入校验、错误处理及端点约定。

主要功能

  1. 01通过中间件注入模型
  2. 02使用 Zod 校验输入
  3. 03查询与变更过程模式
  4. 04聚合详情端点
  5. 05路由命名与日志约定

适用场景

  1. 01添加路由过程
  2. 02在一个端点获取关联数据
  3. 03统一路由的错误处理

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/lobehub/lobehub --skill trpc-router

安装后在 Claude Code 中输入/trpc-router确认已加载。

详细介绍

TRPC 路由指南

文件位置

  • 路由:apps/server/src/routers/lambda/<domain>.ts
  • 辅助函数:apps/server/src/routers/lambda/_helpers/
  • Schema:apps/server/src/routers/lambda/_schema/

路由结构

导入
import { TRPCError } from '@trpc/server';
import { z } from 'zod';

import { SomeModel } from '@/database/models/some';
import { authedProcedure, router } from '@/libs/trpc/lambda';
import { serverDatabase } from '@/libs/trpc/lambda/middleware';
中间件:将模型注入 ctx

始终使用中间件将模型注入 ctx,而不是在每个过程内部创建 new Model(ctx.serverDB, ctx.userId)。

const domainProcedure = authedProcedure.use(serverDatabase).use(async (opts) => {
  const { ctx } = opts;
  return opts.next({
    ctx: {
      fooModel: new FooModel(ctx.serverDB, ctx.userId),
      barModel: new BarModel(ctx.serverDB, ctx.userId),
    },
  });
});

然后在过程中使用 ctx.fooModel:

// Good
const model = ctx.fooModel;

// Bad - don't create models inside procedures
const model = new FooModel(ctx.serverDB, ctx.userId);

例外:当模型需要不同的 userId 时(例如,watchdog 遍历多个用户的任务),请直接创建模型。

过程模式
export const fooRouter = router({
  // Query
  find: domainProcedure.input(z.object({ id: z.string() })).query(async ({ input, ctx }) => {
    try {
      const item = await ctx.fooModel.findById(input.id);
      if (!item) throw new TRPCError({ code: 'NOT_FOUND', message: 'Not found' });
      return { data: item, success: true };
    } catch (error) {
      if (error instanceof TRPCError) throw error;
      console.error('[foo:find]', error);
      throw new TRPCError({
        cause: error,
        code: 'INTERNAL_SERVER_ERROR',
        message: 'Failed to find item',
      });
    }
  }),

  // Mutation
  create: domainProcedure.input(createSchema).mutation(async ({ input, ctx }) => {
    try {
      const item = await ctx.fooModel.create(input);
      return { data: item, message: 'Created', success: true };
    } catch (error) {
      if (error instanceof TRPCError) throw error;
      console.error('[foo:create]', error);
      throw new TRPCError({
        cause: error,
        code: 'INTERNAL_SERVER_ERROR',
        message: 'Failed to create',
      });
    }
  }),
});
聚合详情端点

对于需要多个相关数据的视图,创建单个 detail 过程,并行获取所有数据:

detail: domainProcedure.input(idInput).query(async ({ input, ctx }) => {
  const item = await resolveOrThrow(ctx.fooModel, input.id);

  const [children, related] = await Promise.all([
    ctx.fooModel.findChildren(item.id),
    ctx.barModel.findByFooId(item.id),
  ]);

  return {
    data: { ...item, children, related },
    success: true,
  };
}),

这样可以避免 CLI 或前端按顺序发起 N 次请求。

约定

  • 返回结构:查询使用 { data, success: true },变更使用 { data?, message, success: true }
  • 错误处理:重新抛出 TRPCError,其他错误则使用 console.error + 新建 TRPCError 包装
  • 输入验证:使用 zod schema,并在文件顶部定义
  • 路由名称:export const fooRouter = router({ ... })
  • 过程名称:在路由对象内按字母顺序排列
  • 日志前缀:使用 [domain:procedure] 格式,例如 [task:create]

评论

支持 Markdown