REST vs GraphQL vs tRPC: Choosing an API Style in 2026
Three mature ways to build an API, three very different sets of trade-offs. How to choose based on your consumers, your team and your type system — not hype.
Marcus Webb
Senior Writer
Elena Ruiz
Fact Checked
Sooner or later every project needs an API, and in 2026 you have three mainstream, mature, well-documented ways to build one: classic REST, the query language GraphQL, and the TypeScript-native RPC layer tRPC. All three are production-proven. All three have excellent tooling. And picking the wrong one for your situation will tax your team for years.
The good news: the decision is much less about technology than about who calls your API. Let’s build that decision framework, then look at each option honestly.
The sixty-second version
| REST | GraphQL | tRPC | |
|---|---|---|---|
| Best for | Public APIs, unknown clients | Many diverse frontends, big orgs | Internal full-stack TS apps |
| Contract | OpenAPI (optional) | Schema (mandatory) | TypeScript types (automatic) |
| Over/under-fetching | Common | Solved by design | Irrelevant (you own both sides) |
| HTTP caching | Excellent (native) | Hard (POST queries) | Limited |
| Learning curve | Low | Significant | Low (if you know TS) |
| Client freedom | Any language | Any language | TypeScript only |
| Versioning story | /v1, /v2 or headers | Schema evolution, deprecation | Deploy client+server together |
If you only remember one heuristic, make it this:
Strangers → REST. Many frontends → GraphQL. Your own TypeScript monorepo → tRPC.
REST: the boring default that keeps winning
REST isn’t a technology — it’s a set of conventions over HTTP: resources as URLs, verbs as operations, status codes as outcomes. That simplicity is precisely why, decades in, it still powers the overwhelming majority of public APIs.
Where REST is unbeatable:
- Unknown consumers. Anyone with an HTTP client in any language can call you. No SDK, no schema download, no runtime.
- Caching. GET requests cache natively — in browsers, CDNs, proxies — with zero configuration. For read-heavy public data this is an enormous, often decisive advantage.
- Tooling ubiquity. curl, Postman, API gateways, rate limiters, monitoring — everything speaks REST out of the box.
Where REST hurts:
- Over- and under-fetching. The mobile team wants three fields; your endpoint returns forty. The dashboard needs data from five resources; that’s five round trips. You end up bolting on
?fields=parameters and/summaryendpoints — a homemade, worse GraphQL. - No enforced contract. OpenAPI specs are great but optional, and they drift from reality unless you generate code from them (or them from code).
If you’re building a REST API in Node, we walk through the full setup — routing, validation, error handling, auth — in our Express REST API tutorial.
GraphQL: a query language, and an organizational tool
GraphQL inverts control: the server publishes a typed schema of everything available, and each client composes queries for exactly the data it needs — no more, no less, in one round trip.
query ArticlePage($slug: String!) {
article(slug: $slug) {
title
publishedAt
author { name avatarUrl }
related(limit: 3) { title slug }
}
}
That’s a page’s entire data requirement in one request, shaped by the consumer. Change what the page shows, change the query — no backend deploy.
Where GraphQL genuinely earns its keep:
- Multiple heterogeneous clients. Web, iOS, Android, smart TV, partners — each with different data needs from the same graph. This is the environment GraphQL was invented for at Facebook, and it remains the honest use case.
- Organizational decoupling. Frontend teams self-serve instead of filing “please add a field” tickets. In large companies this changes team dynamics more than any technical property.
- A mandatory, introspectable contract. The schema is the documentation, and tooling (codegen, IDE autocomplete, linting against breaking changes) hangs off it beautifully.
The costs, honestly stated:
- Server complexity. Resolvers, N+1 query problems (hello, DataLoader), query depth limiting, cost analysis to stop hostile queries — a production GraphQL server is a genuine piece of infrastructure.
- Caching regression. Queries are usually POSTs to one endpoint, so HTTP caching is gone; you rebuild caching at the application layer (persisted queries help).
- Complexity floor. For a single web frontend talking to its own backend, you’re paying the schema/resolver/codegen tax to solve coordination problems you don’t have.
tRPC: delete the API layer (for yourselves)
tRPC asks a sharp question: if your client and server are both TypeScript and deploy together, why maintain a serialization contract at all? Define procedures on the server; call them from the client as typed functions:
// server
export const appRouter = router({
articleBySlug: publicProcedure
.input(z.object({ slug: z.string() }))
.query(({ input }) => db.article.findUnique({ where: { slug: input.slug } })),
});
// client — fully typed, autocompleted, no codegen step
const article = await trpc.articleBySlug.query({ slug: 'rest-vs-graphql-vs-trpc' });
Rename a field on the server and the client fails to compile. That end-to-end type safety — with no schema files and no code generation — is the entire pitch, and for full-stack TypeScript teams it’s a massive quality-of-life and correctness win.
The trade-offs are the flip side of the coupling:
- TypeScript-only. The moment a Swift app, a partner, or a Python service needs your API, tRPC has nothing for them.
- Client and server must move together. Fine in a monorepo with atomic deploys; painful across independently-released apps (mobile app stores, anyone?).
- It’s RPC. You get functions, not a resource model — which is honest for internal use, but public APIs benefit from REST’s uniform interface.
Worth noting: React Server Components and server actions (covered in our server vs client components guide) now handle some of the same internal data-fetching territory, which is exactly why tRPC’s sweet spot is mutations-heavy, client-rich apps and non-Next stacks.
The decision, step by step
- Will unknown third parties call this API? → REST (with OpenAPI). End of analysis for the public surface.
- Do three-plus different client teams consume overlapping data? → GraphQL is likely worth its infrastructure.
- Is it your own TypeScript app, front and back in one repo? → tRPC (or server actions if you’re all-in on RSC).
- Read-heavy public content where CDN caching matters? → REST, no contest.
- Still unsure? → REST. It’s the choice you’ll regret least, and every developer you ever hire already knows it.
And remember these compose. A very common, very sane 2026 architecture: tRPC internally for the product app, a REST facade for public/partner access, and GraphQL only if/when client diversity actually materializes. Choosing an API style is not a wedding.
Performance footnote
None of the three is “fastest” in general. REST wins read-heavy public workloads via HTTP caching; GraphQL wins chatty mobile screens by collapsing round trips; tRPC’s batching gives snappy internal apps with minimal effort. The real performance decision is usually where the data is served from — edge caching and static generation dwarf protocol differences, as our hosting platforms comparison shows in concrete numbers.
Final Thoughts
API style is a people decision wearing a technology costume. REST optimizes for strangers, GraphQL for many known teams, tRPC for one team moving fast in one language. Map your actual consumers — today’s and the realistic next two years’ — onto that sentence and the choice usually makes itself.
The one genuine mistake is choosing by fashion: adopting GraphQL for a solo project’s single frontend, or wedging tRPC into a public API. Boring correctness beats interesting architecture — your API is the part of your system you’ll live with longest.