はじめに
OpenAPI specを書かずにクライアント-サーバー間の型安全を実現する——tRPC + Zod + Prismaでエンドツーエンドの型推論を設計する。Claude Codeに生成させる。
tRPC + Zod + Prisma の設計パターン
// CLAUDE.mdに書くルール:
// - Zodスキーマで入力バリデーション(実行時+コンパイル時)
// - publicProcedure / protectedProcedure の2段階
// - protectedProcedureはJWT検証ミドルウェア必須
生成される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 })
),
});
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);
},
});
まとめ
- CLAUDE.md にpublicProcedure/protectedProcedureの2段階・Zodバリデーション・Prisma Select型を明記
- postSelect satisfies Prisma.PostSelect でコンパイル時に型安全なフィールド選択
- 所有権チェック をupdate/deleteプロシージャで統一パターン化
- 楽観的更新 でonMutate/onError/setDataパターンを標準化
型設計のレビューは **Code Review Pack(¥980)* の /code-review で確認できます。*
Top comments (0)