mcpField
T
trpc-router

trpc-router

github.com/lobehub開発83.6k127 回インストール今日更新
リポジトリ

trpc-router について

サーバーコードで tRPC ルーターを実装するためのガイドです。プロシージャ構成、中間ウェアによるモデル注入、入力検証、エラー処理、エンドポイントの規約を説明します。

主な機能

  1. 01中間ウェアによるモデル注入
  2. 02Zod による入力検証
  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/
  • スキーマ: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 に注入する

各プロシージャ内で new Model(ctx.serverDB, ctx.userId) を作成するのではなく、必ずミドルウェアを使ってモデルを ctx に注入してください。

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 が必要な場合(例:ウォッチドッグで複数のユーザーのタスクを反復処理する場合)は、インラインで作成します。

プロシージャのパターン
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 スキーマを使用し、ファイルの先頭で定義します
  • ルーター名:export const fooRouter = router({ ... })
  • プロシージャ名:ルーターオブジェクト内でアルファベット順にします
  • ログのプレフィックス:[domain:procedure] 形式(例:[task:create])

コメント