API Paradigms: REST vs GraphQL vs tRPC for Modern TypeScript Stacks
Evaluating developer velocity, client schema compilation, and network caching characteristics.
1. End-to-End Type Safety without Code Generation
tRPC's core insight is that if your client and server share a TypeScript monorepo, you don't need a schema definition language, a codegen step, or a runtime type-checking layer at all — the TypeScript compiler itself can be the contract. A tRPC router exports its procedure types, the client imports that type (never the implementation), and calling `trpc.user.getById.query({ id })` is fully typed end to end, including inference for optional fields, discriminated unions, and Zod-validated input shapes.
This eliminates an entire category of drift bugs: the classic case where a backend renames a field, the OpenAPI spec regenerates, but the frontend's hand-written fetch call still references the old field name and fails silently at runtime. With tRPC, that rename is a compile error in the frontend the moment you save the file, before you've even run the app. For a small team shipping fast in a single repo, this feedback loop is worth more than almost any other API tooling investment.
The catch is the monorepo requirement. tRPC's type inference only works because the client can literally import the server's TypeScript types at build time. If your mobile team, your public API consumers, or a partner integration need the same backend, you're back to needing a language-agnostic schema — which is precisely what tRPC intentionally does not provide. It is an internal-tool and first-party-client protocol, not an interoperability protocol.
This also affects deployability in ways teams underestimate. Because tRPC's types flow through TypeScript's structural typing at build time, splitting the frontend and backend into separate repositories (a common move as an organization grows) requires publishing the router's types as a versioned package and manually keeping client and server deploys in lockstep, which reintroduces much of the coordination overhead tRPC was chosen to avoid in the first place. Teams that expect to split repositories within a year or two should weigh this migration cost against the near-term velocity win.
2. When GraphQL Still Shines
GraphQL solves a specific problem that neither REST nor tRPC address well: client-driven field selection across a deeply nested, product-owned graph consumed by multiple independent frontends. A mobile app, a web dashboard, and a third-party integration can each request exactly the fields they need from the same `Post` type — the mobile client skips the full comment thread, the dashboard requests aggregate counts — without the backend team maintaining N bespoke endpoints or a client shipping unused payload weight.
This matters most in organizations where frontend and backend teams are organizationally separate and iterate independently. A published GraphQL schema is a genuine contract that survives team boundaries in a way tRPC's compile-time inference cannot, because GraphQL is language-agnostic and doesn't require the consumer to share a build system with the producer. Tools like GraphQL Code Generator recover much of tRPC's type-safety benefit for TypeScript clients specifically, generating typed hooks from `.graphql` operation files against the published schema.

