Introduction
Type safety is crucial for building reliable applications. In this post, I'll walk through implementing complete end-to-end type safety in a Next.js application using tRPC.
Why tRPC?
Traditional REST APIs lose type information between client and server. Even with TypeScript on both sides, you're manually keeping types in sync or using code generation.
tRPC solves this by sharing types directly between client and server—no codegen, no sync issues, just pure TypeScript.
Setup
First, install the dependencies:
pnpm add @trpc/server @trpc/client @trpc/react-query @trpc/next
pnpm add zod
##Router Definition
Create a tRPC router with full type safety:
import { initTRPC } from "@trpc/server";
import { z } from "zod";
const t = initTRPC.create();
export const appRouter = t.router({
getUser: t.procedure
.input(z.object({ id: z.string() }))
.query(async ({ input }) => {
// Full type safety here
const user = await db.user.findUnique({
where: { id: input.id },
});
return user;
}),
});
export type AppRouter = typeof appRouter;
Client Usage
On the client, you get full autocomplete and type checking:
const { data } = trpc.getUser.useQuery({ id: "123" });
// ^? const data: User | undefined
Benefits
- Zero Runtime Overhead: Types are compile-time only
- Autocomplete Everywhere: IDE knows all available procedures
- Refactoring Safety: Rename a field, catch all usages
- Validation Built-in: Zod schemas validate at runtime
Conclusion
tRPC + Zod + Prisma creates an incredibly productive development experience with bulletproof type safety from database to UI.