REST, tRPC, or GraphQL: The Real Decision Is Who Controls the Contract

The technology choice matters less than the team structure it implies. Most teams pick the wrong tool because they're solving a technical problem that's actually organizational.
Every few months the conversation surfaces again: REST vs. GraphQL, or now REST vs. tRPC, or some combination of all three. The discussion is usually framed as a technical comparison — flexibility vs. simplicity, type safety vs. developer familiarity, over-fetching vs. schema complexity. Those are real tradeoffs. But in my experience, teams that make this choice purely on technical grounds often end up with a setup that works fine architecturally but creates constant friction in practice.
The question that actually predicts whether an API design choice will work long-term isn't about the technology. It's about ownership: who decides what the contract between client and server looks like, and how do changes to that contract get made?
REST, GraphQL, and tRPC encode different assumptions about where power should live in the client-server relationship. Pick the one that matches your team structure.
REST: the server decides
A well-designed REST API is designed by the server team and consumed by whoever needs it. The server defines the resources, the endpoints, and the shape of the responses. Clients adapt to that contract. This model works well when the API is genuinely public-facing or when the server team has legitimate domain ownership over the data model.
Where it breaks down is in product teams where frontend and backend are tightly coupled but organizationally separate. The frontend needs a field that doesn't exist. Getting it added requires a meeting, a ticket, a PR on the backend, a deployment, and then the frontend can ship. For fast-moving products, that cycle is expensive. Teams paper over it with catch-all endpoints that return everything and let the client filter, which creates the over-fetching problem that GraphQL was invented to solve.
GraphQL: the client negotiates
GraphQL inverts the control: the client declares exactly what data it needs, and the server figures out how to provide it. This solves the over-fetching problem cleanly and gives frontend teams genuine autonomy over what they request. For product teams iterating quickly on a complex UI with many data requirements, this is a real productivity gain.
The hidden cost is the server-side complexity. Someone has to maintain the schema, write resolvers, handle authorization at the field level, and manage the N+1 query problem that appears almost immediately in any non-trivial implementation. GraphQL moves the frontend's problem (waiting for endpoints) to the backend (maintaining a flexible schema). Whether that's a good trade depends on your team's structure and priorities.
- Full-stack team, TypeScript on both sides: tRPC — end-to-end type safety with minimal overhead
- Frontend-heavy product team with a shared API: GraphQL — gives frontend control without waiting on backend
- Public API or multiple clients with different needs: REST — predictable, widely understood, easy to version
- Microservices with varied backends: REST or gRPC at service boundaries, BFF layer for the frontend
tRPC: the boundary disappears
tRPC takes a different approach: if you're using TypeScript on both sides, why have a protocol at all? You define procedures on the server as typed functions, and the client calls them directly with full type inference. No schema generation step, no code generation, no HTTP method conventions to argue about. A change to the server procedure is immediately reflected as a type error in the client code that calls it.
tRPC is the right answer for full-stack TypeScript projects where the frontend and backend are in the same codebase or owned by the same team. It's the wrong answer for public APIs, polyglot stacks, or teams where the API contract needs to be explicitly versioned and communicated. It works by erasing the boundary between client and server, which is a feature when that boundary is artificial and a problem when it needs to be real.
The mistake to avoid
Choosing GraphQL because it sounds more sophisticated, or REST because it's what you know, or tRPC because the tutorial looked clean. The best API design is the one that encodes your actual team structure and ownership model. A beautiful GraphQL schema maintained by a team that doesn't have the resources to manage it properly will cause more pain than a boring REST API that fits how the team actually works.
Start from the organizational question — who owns the contract, who can change it, and how fast do those changes need to happen — and the technical choice usually follows naturally.