REST vs GraphQL in 2026 — When to Pick Each One
Photo by Kevin Ache on Unsplash
Table of Contents
The Architecture Call I Got Wrong Twice
I've been on both sides of this argument. In 2019 I pushed a startup to migrate from a clean REST API to GraphQL because everyone at conferences was raving about it. Six months later I'd spent more time fighting DataLoader batching, debugging query depth limits, and explaining schema directives than I'd ever saved on "fewer roundtrips."
Then in 2022 I joined a team that was building REST endpoints for a mobile app and a web app and a partner integration — three clients with three completely different data needs from the same backend. Each was making 4-7 sequential REST calls per screen. GraphQL would have killed those waterfalls. We didn't use it. I was wrong then too.
I've since had this conversation with maybe two dozen teams. Here's the honest version, written without anyone trying to sell me a query engine.
What REST and GraphQL Actually Do Differently
GraphQL is a query language and a runtime. You expose a single endpoint (`/graphql`) and a schema that describes every type and field. Clients send queries that name exactly the fields they want and receive a JSON response shaped to match. One request, many resources, no over-fetching.
That's the surface. The deeper differences are where the tradeoffs live.
REST piggybacks on HTTP. Status codes carry meaning (`404` for not found, `409` for conflict, `429` for rate-limited). Caching works out of the box because every URL is its own resource — a CDN can cache `/users/123` and serve it from edge nodes. ETags let clients ask "has this changed?" with conditional requests. Browsers, intermediaries, and tools like cURL all understand REST natively.
GraphQL throws most of that away. Everything is a `POST` to one endpoint (mutations and queries both), so HTTP-level caching needs a custom layer (persisted queries, query whitelisting, or something like Apollo's response cache). Status codes are almost always `200` even when the query failed — errors come back inside the JSON body. Conditional requests don't really map to the model.
In exchange, GraphQL gives you something REST can't: the client describes the response shape. No more 12 KB JSON payloads when you wanted three fields. No more "can you add this field to the endpoint" round trips with the backend team. The schema is self-documenting and the introspection API powers GraphiQL, code generation, and type-safe clients.
When REST Is the Right Call (Most of the Time)
Photo by Nathan Watson on Unsplash
You're building a public API. Stripe, Twilio, GitHub — they all use REST. Public APIs need to be approachable from every language, every framework, every CLI tool. `curl https://api.stripe.com/v1/charges/ch_123` works. The GraphQL equivalent involves crafting a query, picking a client, and learning a new mental model. REST has the lower learning curve for outside developers, and a public API lives or dies on its developer experience.
You need HTTP caching. If 80% of your reads can sit in a CDN cache for 60 seconds, REST gives you that for free with proper `Cache-Control` headers. GraphQL needs persisted queries (a hash that maps to a known query) before you can cache at the edge, and setting that up adds complexity. For high-traffic content APIs, the caching delta often pays for itself.
Your team is small and the resources are simple. Five tables, twenty endpoints, one client. The overhead of a schema language, a query engine, and resolver wiring is pure cost when you could ship a clean REST API in an afternoon. I've watched two-person teams adopt GraphQL because it felt modern, and they ended up spending more time on infrastructure than on features for the next six months.
You're worried about query-complexity attacks. GraphQL's flexibility cuts both ways. A naive server lets clients write a query that joins five tables, fetches 10,000 rows, and includes 12 nested relationships. Without query depth limits, complexity scoring, and rate limiting, you're shipping a free DoS button. REST endpoints are inherently bounded — each one returns a known shape with a known cost. For trust-boundary-crossing APIs, that's a real security advantage.
If you're new to API design, my What Is an API explainer walks through the request/response lifecycle from scratch — it's REST-flavored because REST is the right starting point.
When GraphQL Actually Wins
Different clients want different slices of the same data. A mobile app shows a compact user card with avatar, name, and badge count. The web app shows a full profile with bio, follower list, and recent activity. The partner integration just wants user IDs and emails. With REST, you're either over-fetching for some clients or building three custom endpoints. With GraphQL, each client writes its own query against the same schema.
The frontend team is decoupled from the backend. If client engineers are blocked waiting on backend endpoints to land, GraphQL lets them add new fields to existing queries without a backend ticket. The cost is upfront — you need a well-designed schema and clear ownership of types — but once it's there, frontend teams move noticeably faster.
The UI is a graph of relationships. Social products, content management, anything with deep nesting. "Give me this user's posts, their comments, the commenters' avatars, and the count of likes on each comment" is a four-or-five-roundtrip REST query and a single GraphQL query. The latency wins compound across mobile networks.
You're shipping a developer-facing GraphQL API. Some products (Shopify, GitHub's V4 API, Hasura) ship GraphQL because their users genuinely benefit from query flexibility. If your API is a product itself and your users are technical, GraphQL is a reasonable surface.
I've also used GraphQL as a BFF (backend-for-frontend) layer that aggregates multiple REST microservices into one client-facing endpoint. That pattern works well — you keep clean REST inside the trust boundary and use GraphQL where the shape mismatch hurts.
The Costs Nobody Mentions on GraphQL Marketing Pages
Photo by Goran Ivos on Unsplash
The N+1 problem. A naive GraphQL resolver fetches user `123` from the DB, then fetches `123`'s posts (one query), then iterates over the posts and fetches each author (N queries). You end up doing 100 DB queries to serve one client request. The fix is DataLoader — a batching layer that collects all author fetches in a tick and issues one batched query. It works, but it's not free, and forgetting it is the easiest performance bug in GraphQL.
Query complexity scoring. Once you accept arbitrary client queries, you have to bound them. Every production GraphQL server I've worked on eventually adopted a complexity scorer (graphql-cost-analysis or similar). Each field gets a weight; each query gets a total score; queries above a threshold get rejected. It's another moving part to maintain and tune.
Client bundle size. Apollo Client, Relay, and similar fully-featured clients add 30-80 KB gzipped to your bundle. For a single-page app that's already heavy, fine. For a content site where every kilobyte matters, that's brutal. There are lighter clients (graphql-request is great), but you give up the cache and the dev tools.
There's also the cost of explaining GraphQL to every new hire. REST is universally understood. GraphQL is not — there's a learning curve, a vocabulary (resolvers, schemas, fragments, persisted queries), and a class of bugs (N+1, over-fetching at the resolver layer, schema drift) that doesn't exist in REST. If your team is rotating fast, that cost is real.
The official GraphQL spec is worth skimming if you're seriously considering it — most of the surprises are documented if you read carefully.
A Quick Decision Tree I Use
1. Is this a public API? → REST. 2. Is the team smaller than five engineers? → REST. 3. Are there multiple clients with very different data shapes? → consider GraphQL. 4. Is the UI a graph of relationships with deep nesting? → consider GraphQL. 5. Do you need aggressive edge caching? → REST. 6. Are you a frontend team blocked on backend changes? → consider GraphQL as a BFF layer. 7. Are you choosing because it sounds modern? → REST.
GraphQL is a sharp tool. The teams I've seen succeed with it had clear reasons to pick it, owned the operational complexity, and treated schema design as a real discipline. The teams I've seen struggle picked it for vibes.
There's also a middle path nobody talks about enough: REST with sparse fieldsets. You add `?fields=id,name,avatar` to a REST endpoint and the server returns only the requested fields. JSON:API codifies this; Stripe supports an `expand` parameter for the relationships case. You get 70% of GraphQL's flexibility with 10% of the complexity. For most teams I work with, this is the underrated answer.
Tooling around APIs in general is worth investing in regardless of which side you pick. I keep the JWT decoder open whenever I'm debugging auth headers, the URL encoder for query string surgery, and a handful of REST clients in a folder. Pick the tool that fits the problem, not the trend.
Frequently Asked Questions
Is GraphQL faster than REST?
Not inherently. GraphQL reduces the number of HTTP roundtrips by letting one query fetch related data, which helps on slow networks. But each query is more expensive on the server because the resolver has to walk the schema, batch DB calls, and serialize the response. For simple data with HTTP caching, REST is usually faster end-to-end. For complex graph queries on mobile networks, GraphQL wins. Measure on your actual workload — don't pick based on the marketing pages.
Can I use REST and GraphQL together?
Yes, and it's a common pattern in 2026. The two main shapes: GraphQL as a BFF (backend-for-frontend) that aggregates multiple REST microservices behind one client-facing schema, or REST for public API surfaces with GraphQL for the internal dashboard. Don't expose both for the same internal use case — you'll double the maintenance and confuse new hires. If you're new to API basics, the [what is an API article](/blog/what-is-an-api-how-it-works) is a good starting point.
What is the N+1 problem in GraphQL?
It's the most common GraphQL performance bug. A query like `users { posts { author { name } } }` triggers one query for users, one query per user for posts, and one query per post for authors — exploding to hundreds of DB queries. The fix is DataLoader, a batching layer that collects pending fetches and issues them as one query per type per request. It's standard in production GraphQL servers but easy to forget on new resolvers.
Do I need a special client for GraphQL?
Not strictly. You can `fetch('/graphql', { method: 'POST', body: JSON.stringify({ query }) })` from anywhere. But heavy clients like Apollo or Relay add caching, dev tools, optimistic updates, and TypeScript code generation. Lighter clients like graphql-request give you just the request. For a small app, native fetch is fine; for a large app, the heavy clients earn their bundle size.
Is REST going away?
No. REST is the dominant API style in 2026 and almost every public API on the internet still uses it. GraphQL has a strong niche in product-internal APIs and developer-facing platforms. gRPC owns service-to-service communication in many backends. tRPC is rising fast for full-stack TypeScript apps. The honest answer is that the API world is more pluralistic than it was in 2018, and REST remains the safe default for most projects.
Can GraphQL queries be cached?
Yes, but it's harder than REST. The standard approach is persisted queries — the client sends a hash of a pre-registered query instead of the query text, and the server (or a CDN) caches responses keyed by hash. Apollo, Relay, and Hasura all support this. Without persisted queries, you're stuck with in-memory client caches like Apollo's normalized cache, which work but don't reach the edge.
Try ToolsFuel
23+ free online tools for developers, designers, and everyone. No signup required.
Browse All Tools