DEV Community

myougaTheAxo
myougaTheAxo

Posted on • Originally published at zenn.dev

Claude CodeでEnd-to-End型安全APIを設計する:tRPC・Zod・Prisma統合

はじめに

OpenAPI specを書かずにクライアント-サーバー間の型安全を実現する——tRPC + Zod + Prismaでエンドツーエンドの型推論を設計する。Claude Codeに生成させる。

tRPC + Zod + Prisma の設計パターン

// CLAUDE.mdに書くルール:
// - Zodスキーマで入力バリデーション(実行時+コンパイル時)
// - publicProcedure / protectedProcedure の2段階
// - protectedProcedureはJWT検証ミドルウェア必須
Enter fullscreen mode Exit fullscreen mode

生成されるtRPC実装

// src/trpc/init.ts
const t = initTRPC.context<Context>().create({ transformer: superjson });

export const protectedProcedure = t.procedure.use(({ ctx, next }) => {
  if (!ctx.user) throw new TRPCError({ code: 'UNAUTHORIZED' });
  return next({ ctx: { ...ctx, user: ctx.user } });
});

// src/trpc/routers/posts.ts
export const postsRouter = router({
  list: publicProcedure
    .input(z.object({ cursor: z.string().optional(), limit: z.number().min(1).max(100).default(20) }))
    .query(async ({ ctx, input }) => {
      const items = await ctx.prisma.post.findMany({
        select: postSelect,
        take: input.limit + 1,
        cursor: input.cursor ? { id: input.cursor } : undefined,
        orderBy: { createdAt: 'desc' },
      });
      return { items: items.slice(0, input.limit), nextCursor: items.length > input.limit ? items[input.limit].id : undefined };
    }),

  create: protectedProcedure
    .input(z.object({ title: z.string().min(1).max(200), content: z.string().min(1) }))
    .mutation(async ({ ctx, input }) =>
      ctx.prisma.post.create({ data: { ...input, authorId: ctx.user.id }, select: postSelect })
    ),
});
Enter fullscreen mode Exit fullscreen mode

React Query統合(楽観的更新)

// 楽観的削除
const deletePost = trpc.posts.delete.useMutation({
  onMutate: async ({ id }) => {
    await utils.posts.list.cancel();
    const previous = utils.posts.list.getData();
    utils.posts.list.setData({ limit: 20 }, old => ({
      ...old!, items: old!.items.filter(p => p.id !== id),
    }));
    return { previous };
  },
  onError: (_, __, ctx) => {
    if (ctx?.previous) utils.posts.list.setData({ limit: 20 }, ctx.previous);
  },
});
Enter fullscreen mode Exit fullscreen mode

まとめ

  1. CLAUDE.md にpublicProcedure/protectedProcedureの2段階・Zodバリデーション・Prisma Select型を明記
  2. postSelect satisfies Prisma.PostSelect でコンパイル時に型安全なフィールド選択
  3. 所有権チェック をupdate/deleteプロシージャで統一パターン化
  4. 楽観的更新 でonMutate/onError/setDataパターンを標準化

型設計のレビューは **Code Review Pack(¥980)* の /code-review で確認できます。*

prompt-works.jp

Top comments (0)