End-to-End Type Safety with tRPC and Prisma
How to wire tRPC, Prisma, and Zod together so a DB schema change surfaces as a TypeScript error before it ever hits production.
The type-safety gap between backend and frontend
Even in a TypeScript monorepo, the API boundary is typically untyped. You write a REST endpoint that returns a User object, then on the client you cast the JSON response to a User type and hope they stay in sync. They never do.
tRPC closes this gap by generating the client's type definitions directly from the server's router. No code generation step, no separate schema file — it's just TypeScript inference.
Prisma as the database source of truth
Prisma's schema.prisma file becomes the authoritative definition of your data. Running prisma generate emits fully typed client methods — so querying the database returns properly-typed objects, not any.
// server/routers/user.ts
import { z } from 'zod';
import { router, publicProcedure } from '../trpc';
import { prisma } from '../db';
export const userRouter = router({
byId: publicProcedure
.input(z.object({ id: z.string().cuid() }))
.query(({ input }) => prisma.user.findUnique({ where: { id: input.id } })),
});Zod for input validation
Zod schemas serve double duty: they validate the runtime input (so clients can't send garbage) and they infer TypeScript types for input objects. You write the schema once and get both benefits.
The cascade when you change a schema
- Update schema.prisma
- Run prisma migrate dev
- prisma generate updates the Prisma client types
- tRPC procedures using those types turn red in TypeScript
- You fix the procedures — and the client callers turn red too
- Fix the client — and you're done, with confidence
That cascade is the whole point. The type error is your test suite. By the time you're green, you've touched every layer that cares about the change.
tRPC isn't magic. It's just TypeScript inference applied consistently from the database schema to the last line of client code. The result feels like magic because finding bugs at the type level is so much cheaper than finding them in production.