almessadi.
Back to Index

tRPC Is Best When the Whole Stack Already Speaks TypeScript_

tRPC removes a lot of API duplication in full-stack TypeScript systems, but it shines only when the architectural boundaries are already close.

PublishedJuly 28, 2024
Reading Time11 min read

tRPC gets praised as if it replaces every API style. It does not.

What it actually does very well is remove unnecessary coordination work inside a tightly coupled TypeScript stack. If your frontend and backend already live close together, and both are written in TypeScript, tRPC can cut a lot of duplicated typing and interface drift.

That is a real advantage. It is just not universal.

The Problem It Solves

In a lot of full-stack TypeScript apps, the same contract gets described three times:

  • input validation
  • backend handler types
  • frontend consumer types

That duplication is where bugs and stale assumptions creep in.

tRPC collapses a lot of that by letting the client infer procedure types directly from the server router:

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 }) => {
      return db.user.findById(input.id);
    }),
});

export type AppRouter = typeof appRouter;

On the client side, you no longer hand-maintain a second version of the contract:

import { createTRPCProxyClient, httpBatchLink } from "@trpc/client";
import type { AppRouter } from "./server";

const trpc = createTRPCProxyClient<AppRouter>({
  links: [httpBatchLink({ url: "/api/trpc" })],
});

const user = await trpc.getUser.query({ id: "123" });

That is the part people like: fewer handwritten seams.

Why It Feels So Good

tRPC is excellent when:

  • the app is a monorepo or close equivalent
  • TypeScript is the language on both sides
  • the frontend is not meant to be a broad public API consumer

In that world, you get a very fast feedback loop. Rename a field on the server, and the client usually breaks at compile time instead of a week later in QA.

The Trade-Offs

tRPC is weaker when:

  • multiple languages need to consume the API
  • the API is public or partner-facing
  • the backend and frontend evolve independently
  • protocol-level contracts matter more than type inference convenience

That is where REST or gRPC usually makes more sense. Not because they are more modern, but because they are more decoupled.

A Better Framing

tRPC is not "the death of REST."

It is a very good fit for a specific category of product team:

  • one organization
  • one stack
  • one shared type system

If that is your situation, it is a strong option.

If it is not, forcing tRPC into the architecture usually creates a different kind of coupling problem.

Further Reading