Command Palette

Search for a command to run...

All postsEngineering

Why I Switched from REST to tRPC

SB
Sachin Babu
Jan 14, 20256 min read

I've written a lot of REST APIs. I've hand-authored OpenAPI specs, generated clients from them, and watched the generated code drift from the actual server within a sprint. I've debugged runtime undefined is not an object errors that a type system should have caught at compile time. tRPC fixes all of this, and the DX improvement is real.

The Core Problem with REST in TypeScript Monorepos

In a Next.js fullstack project, your server and client are in the same repo, often the same package. Yet by default, there's a serialization boundary with zero type enforcement. You define a route that returns { user: User }, and the calling component just does (data as any).user or writes a manual type assertion and hopes for the best.

OpenAPI + codegen is the traditional answer. It works, but it's friction: keep the spec in sync, run the generator, commit the generated client, handle the inevitable drift.

What tRPC Actually Does

tRPC doesn't generate code. It uses TypeScript's type inference across the network call itself. Your router is the source of truth, and the client infers the input and output types from it at the type level — not at runtime.

// server/routers/user.ts
export const userRouter = router({
  byId: publicProcedure
    .input(z.object({ id: z.string() }))
    .query(async ({ input }) => {
      return db.user.findUnique({ where: { id: input.id } });
    }),
});

// client component
const { data } = api.user.byId.useQuery({ id: userId });
// data is typed as User | null — no assertion needed

Change the return shape on the server, and TypeScript immediately flags every call site that breaks. No codegen step. No drift.

Zod as the Contract

Input validation via Zod does double duty: it's both the runtime validator on the server and the type source for the client. A single schema defines what's allowed in and what TypeScript assumes out.

When to Stick with REST

tRPC is a TypeScript-first, monorepo-first solution. If you're building a public API consumed by external clients in multiple languages, REST (or GraphQL) is the right choice. If you're shipping a mobile app that can't share the router type, tRPC breaks down. And if your team isn't on TypeScript, the whole premise collapses.

But for Next.js fullstack apps with a shared codebase? tRPC removes an entire category of bugs before they can exist.

All postsEnd of article

Keep Reading

All Posts →