REST vs GraphQL vs tRPC: What I Actually Use and Why in 2026

Two years ago I migrated a perfectly functional REST API to GraphQL because I read too many blog posts about how REST was a relic and GraphQL was the future. The migration took three weeks. I spent another two weeks debugging the N+1 query problems that came with it. Then I spent a week implementing DataLoader to fix the performance issues I had introduced by migrating.

The API served a simple dashboard with six endpoints. It did not need flexible query capabilities. It did not aggregate data from multiple sources. It did not serve a mobile client with different data needs than the web client. It served one frontend with predictable data requirements. REST was the right choice from the start.

I learned something from that experience that no comparison article had told me: the best API layer is not the one with the most features. It is the one that matches your actual constraints. And most developers, including me at the time, choose based on hype instead of constraints.

This is the decision framework I wish I had before that migration.


The Comparison Table Trap

Every “REST vs GraphQL vs tRPC” article starts with a comparison table. Features on the left, checkmarks on the right. GraphQL gets the checkmark for flexible queries. REST gets the checkmark for caching. tRPC gets the checkmark for type safety.

These tables are technically accurate and practically useless.

They tell you what each technology can do. They do not tell you which problems you actually have. And the gap between “this technology supports feature X” and “I need feature X in my specific project” is where most bad architecture decisions live.

The developer building a public API for third-party integrations has fundamentally different constraints than the developer building a full-stack TypeScript app in a monorepo. The developer aggregating data from twelve microservices has different needs than the developer building CRUD endpoints for a dashboard. Putting all of them in front of the same comparison table and expecting them to reach the right conclusion is how you end up with over-engineered GraphQL servers for apps that needed five REST endpoints.

The question is not “which is best?” The question is “who is consuming this API, what are their constraints, and what does my team already know?”


REST Is Not Dead (and Calling It Legacy Is Wrong)

I have seen developers dismiss REST as outdated in the same breath they struggle to implement basic caching in their GraphQL server. REST is boring. REST is also the most battle-tested, best-understood, and most widely supported API pattern in existence.

Here is what REST gives you that the newer options do not:

Native HTTP caching. REST’s URL-per-resource model aligns perfectly with HTTP caching semantics. CDNs, browser caches, and proxy caches all understand GET /products/123. This is not a minor advantage. For read-heavy applications, HTTP caching can eliminate entire categories of performance problems without writing a single line of cache management code.

Universal tooling support. Every programming language, every HTTP client, every monitoring tool, every API gateway understands REST. Your API will work with clients written in Python, Go, Rust, JavaScript, Swift, or anything else without any additional setup. When you build a public API, this matters enormously.

Simple mental model. Resources map to URLs. HTTP methods map to operations. Status codes map to outcomes. A junior developer can understand a REST API in hours. The onboarding cost is nearly zero.

Proven at scale. The largest APIs on the internet, Stripe, Twilio, GitHub’s v3, are REST APIs. Not because those companies did not consider alternatives. Because REST’s simplicity scales well operationally.

The tradeoffs of REST are real. Over-fetching and under-fetching are genuine problems when your frontend needs vary significantly from your resource structure. Versioning REST APIs is painful. And building a good REST API still requires discipline around consistent response shapes, proper error handling, and meaningful status codes.

But the solution to those tradeoffs is not always GraphQL. Sometimes it is a better-designed REST API. The stop obsessing over the perfect stack advice applies here too. A well-designed REST API beats a poorly implemented GraphQL server every time.


GraphQL: Incredible When It Fits, Painful When It Does Not

GraphQL enterprise adoption grew 340% since 2023. That number is impressive and also misleading, because it does not tell you how many of those adoptions went smoothly.

I have used GraphQL on three production projects. One was a great fit. Two were not. The one that worked was a product with a complex, relational data model, multiple client types (web, mobile, third-party), and a dedicated frontend team that iterated rapidly on data requirements. GraphQL’s ability to let the client specify exactly what it needs was genuinely valuable there.

The two that were not great fits were simpler applications where the data requirements were predictable and stable. In both cases, GraphQL added complexity without adding proportional value.

Here is where GraphQL genuinely shines:

Complex, relational data with multiple consumers. When your frontend needs to traverse relationships, for example loading a user with their posts, comments, and notification preferences in a single request, GraphQL eliminates the waterfall of REST calls. The client describes the data shape it needs and gets exactly that.

Backend-for-Frontend patterns. When you have multiple frontend clients with different data needs, GraphQL lets each client request exactly what it needs without building separate endpoints. A mobile client that needs minimal data and a desktop client that needs everything can share the same API.

Rapid frontend iteration. When the frontend team is iterating faster than the backend team can ship new endpoints, GraphQL removes the dependency. The frontend team can adjust its queries without waiting for backend changes. On teams where this bottleneck is real, GraphQL’s productivity impact is significant.

Here is where GraphQL costs more than it saves:

N+1 queries. Without DataLoader or equivalent batching, a query that loads 100 users with their posts generates 101 database round trips. This is not a theoretical problem. It is the most common performance issue in production GraphQL systems. Solving it requires additional infrastructure that REST simply does not need.

Caching is hard. GraphQL uses POST requests by default. POST requests bypass HTTP caching. You need specialized caching strategies (persisted queries, response-level caching, or CDN-specific integrations) to get caching behavior that REST gives you for free.

Security surface area. Arbitrary query depth and complexity means a malicious or careless client can construct queries that overload your server. You need query complexity analysis, depth limiting, and rate limiting at the query level. This is operational overhead that scales with the flexibility you are offering.

Onboarding cost. REST takes days to learn. GraphQL takes weeks. The schema definition language, resolvers, DataLoader, fragments, subscriptions, and the mental model of graph traversal are all concepts that need to be understood before a developer is productive. For small teams, this cost is not trivial.

The 2026 consensus among teams I talk to is that GraphQL has found its niche. It is not replacing REST universally. About 67% of large organizations use both GraphQL and REST, treating them as complementary rather than competing. GraphQL for client-facing BFF layers. REST for everything else.


tRPC: The Best API Layer Nobody Outside TypeScript Knows About

If you write TypeScript on both your frontend and backend, tRPC might be the most productive API layer available in 2026. And if you do not write TypeScript on both sides, tRPC is not an option at all. That constraint defines everything about when to use it.

tRPC eliminates the API layer as a concept. You define functions on the server. You call those functions from the client. TypeScript infers the types across the boundary automatically. No schema file. No code generation. No runtime validation layer between client and server. You change a function signature on the server and your IDE immediately shows type errors on every client call site.

The experience of using tRPC in a monorepo is hard to describe until you have tried it. The boundary between frontend and backend stops feeling like a boundary. You are invoking functions, not calling APIs. The feedback loop is instant because the type checker catches contract violations at compile time, not at runtime.

Here is what makes tRPC compelling:

Zero-cost type safety. There is no schema to maintain, no types to generate, no codegen step to run. The types are inferred from the actual implementation. This means the types are always correct because they are the code.

Developer experience in monorepos. tRPC’s adoption surge tracks with the maturation of monorepo tooling. Turborepo, Nx, and pnpm workspaces made it practical for small teams to run frontend and backend together. tRPC makes it productive. The combination is genuinely transformative for one-person startup scaling.

Performance. tRPC adds minimal overhead. It is essentially a thin wrapper around HTTP. No query parsing, no schema validation at runtime, no resolver chain. For internal APIs where the client and server are both under your control, the performance characteristics are excellent.

Growing ecosystem. The T3 Stack (Next.js, tRPC, Prisma, Tailwind) has become one of the most popular full-stack patterns in the TypeScript ecosystem. Over 38,000 GitHub stars and a large community of developers building real products with it.

Here is what limits tRPC:

TypeScript only. If your client is not TypeScript, tRPC’s core value proposition disappears. Mobile apps in Swift or Kotlin cannot consume tRPC endpoints without additional tooling. External developers integrating with your API cannot use tRPC unless they are also in the TypeScript ecosystem.

Not for public APIs. tRPC is designed for internal communication between your own frontend and backend. It does not generate OpenAPI specs. It does not produce documentation that external developers can consume. If you need a public API, REST with OpenAPI is still the standard.

Tight coupling by design. tRPC works because the client and server share types. This means they need to be in the same repository or share packages. For distributed teams working across separate repositories, this coupling becomes a constraint rather than a feature.

Learning curve for the pattern. While the API itself is simple, the mental model of “server functions called from the client” requires developers to unlearn some REST habits. Input validation with Zod, middleware chains, and context patterns have their own learning curve.

If you are building a TypeScript full-stack application and your API is consumed only by your own frontend, tRPC is probably the right default choice in 2026. The developer experience advantage is significant enough that it changes how fast you can iterate.


The Decision Framework That Actually Works

Stop asking “which is best?” Start asking these four questions:

Question one: Who is consuming your API?

If external developers, third-party integrations, or non-TypeScript clients need to consume your API, use REST with OpenAPI documentation. Full stop. The ecosystem support, documentation tooling, and universal compatibility are not optional for public APIs.

If only your own frontend team consumes the API and everyone writes TypeScript, tRPC is the strongest choice for internal communication.

If multiple client types with significantly different data needs consume the API, GraphQL is worth the complexity cost.

Question two: How complex is your data model?

If your data is mostly flat resources with simple relationships (users, products, orders with straightforward joins), REST handles this cleanly. A well-designed REST API with a few compound endpoints covers most CRUD applications.

If your data is deeply relational and clients need to traverse multiple levels of relationships in unpredictable ways, GraphQL’s query flexibility provides real value.

If your data model is complex but the query patterns are predictable (you know which relationships clients will need), tRPC or REST with purpose-built endpoints works better than GraphQL because you can optimize the specific queries you know you need.

Question three: What does your team already know?

This matters more than most architects want to admit. A team that knows REST well will build a better REST API than a mediocre GraphQL implementation. A team fluent in TypeScript will get more out of tRPC than a team still learning TypeScript fundamentals.

The productivity cost of learning a new API paradigm is real and often underestimated. If your team has six months of runway and needs to ship fast, use what they know. Optimize the architecture later when you have more information about your actual constraints.

Question four: What is your scaling trajectory?

If you are building a side project or early-stage product, optimize for speed. tRPC in a monorepo if you are TypeScript-only. REST if you need flexibility. Do not add GraphQL unless you have a specific problem it solves.

If you are building infrastructure that needs to last years and serve many consumers, invest in REST with OpenAPI for public surfaces and evaluate GraphQL for internal BFF layers where the data complexity justifies it.


What About gRPC?

I am not covering gRPC in depth because it serves a different use case than the other three. gRPC is optimized for service-to-service communication where bandwidth efficiency and performance matter more than developer experience. It uses Protocol Buffers for serialization, which is significantly more efficient than JSON but requires a compilation step and has a steeper learning curve.

If you are building microservices that need to communicate with low latency and high throughput, gRPC is excellent. If you are building a web application with a frontend, gRPC is not the right tool for the client-facing layer.

Most production architectures in 2026 that use gRPC use it alongside REST or GraphQL: gRPC for internal service communication, REST or GraphQL for client-facing APIs. It is a complement, not a competitor, to the other options.


The Multi-Protocol Reality

The most useful thing I have learned about API design in 2026 is that the answer is almost never “use one protocol for everything.”

The pattern I see in well-architected systems looks like this:

  • Public API: REST with OpenAPI documentation
  • Internal frontend-to-backend: tRPC (TypeScript projects) or GraphQL (multi-client projects)
  • Service-to-service: gRPC or REST, depending on performance requirements
  • AI agent integration: MCP (Model Context Protocol) for tool exposure

Each protocol serves a different boundary with different requirements. Trying to use one protocol everywhere is like using one database for everything. It works until it does not, and then the migration is painful.

The developers I know who are most productive with API design are the ones who can pick the right tool for each boundary without over-thinking it. They use REST where REST is strong, tRPC where type safety matters, and GraphQL where data flexibility is a real requirement. They do not spend weeks debating which is “better” in the abstract.


What I Actually Use on My Projects

For full transparency, here is what I use and why.

Personal projects and side projects: tRPC with Next.js in a monorepo. The developer experience is unmatched for solo development. I do not need a public API. I do not need multi-language client support. I need to move fast with full type safety, and tRPC delivers that better than anything else I have tried.

Client work with API requirements: REST with OpenAPI. When clients need documentation, when external teams will integrate, when the API needs to outlast my involvement in the project, REST is the responsible choice. I use Hono for the server and generate OpenAPI specs automatically.

Complex data products: GraphQL, but only when the data model and client requirements genuinely warrant it. I have learned to be honest about whether I actually need GraphQL’s flexibility or whether I just want to use it because it feels more sophisticated.

The honest pattern is: tRPC for speed when I control both sides, REST for durability when I do not, GraphQL only when the data complexity demands it. Everything else is over-engineering.


The Honest Take

The API design landscape in 2026 is more settled than the blog posts make it seem. REST is not going anywhere. GraphQL found its niche and stopped trying to replace everything. tRPC is the breakout winner for TypeScript teams who do not need public APIs.

The developers who struggle with API design are not the ones who pick the “wrong” technology. They are the ones who pick a technology for the wrong reasons. Choosing GraphQL because it is trendy, or avoiding REST because it feels old, or adopting tRPC because a YouTube video made it look easy. Those are all bad reasons.

Good reasons sound like: “Our mobile and web clients need different data shapes from the same backend, so GraphQL’s client-driven queries save us from maintaining parallel endpoints.” Or: “We are a two-person TypeScript team shipping fast in a monorepo, so tRPC gives us type safety without the overhead of schema management.” Or: “We need a public API that any HTTP client can consume, so REST with OpenAPI is the obvious choice.”

Start with the constraints. Pick the tool that fits. Move on and build the actual product. The API layer is important, but it is not the product. The product is the product.