Tech · 6 min read
REST vs GraphQL vs gRPC: A Working Engineer's Guide to Picking an API Style
A practical, no-hype comparison of REST, GraphQL, and gRPC — what each one optimizes for, where it bites, and how to pick the right tool for the API you're actually building.
By Jarviix Engineering · Apr 19, 2026
Every backend engineer eventually has to pick an API style. The defaults have shifted: REST was the obvious answer for a decade, GraphQL became the cool choice around 2018, and gRPC quietly took over service-to-service traffic inside large systems. None of those is universally "right". The right choice is the one that matches how the API will actually get used.
This post is a calm, opinionated comparison — what each style optimizes for, where it bites, and how to choose without joining a tribe.
The mental model
All three of these are ways for one process to ask another for data or trigger an action. The differences sit on three axes:
- Schema strictness. How much the contract is enforced by the framework vs by convention.
- Transport. HTTP/1.1, HTTP/2, sometimes raw TCP. Determines what's cheap and what's not.
- Client control. How much the caller gets to shape the response.
Once you internalize those three axes, the right choice for a given project usually picks itself.
REST: still the right default
REST is the lingua franca of public web APIs. Resources are URLs. Operations are HTTP verbs. State is stateless across requests. Tooling is universal — curl works, browsers work, every cache and proxy on the internet understands it.
What it's great at:
- Public APIs read by clients you don't control.
- Workloads that map cleanly to "things" (users, orders, posts).
- Anywhere caching at the HTTP layer is valuable — CDNs, reverse proxies, browser caches all natively understand REST + verbs + cache headers.
Where it bites:
- Over-fetching. The resource gives you everything; the mobile client wanted three fields out of forty.
- Under-fetching. The screen needs three resources; you make three round trips. Networks hate round trips.
- Discoverability. Without OpenAPI or hypermedia, "what fields does this endpoint return?" is a wiki page.
Reach for it when you're building an API for unknown future consumers, your data fits the resource model, and you want the broadest possible tooling support.
GraphQL: the right answer for many-shaped clients
GraphQL flips the question. Instead of the server deciding which fields a resource exposes, the client sends a query that says exactly what it wants:
query {
user(id: 42) {
name
posts(last: 5) { title createdAt }
}
}
The server returns precisely those fields, in that shape, in one round trip.
What it's great at:
- One backend serving many clients with different needs (mobile app, web app, partner integrations) — each fetches what it needs without the server team shipping a custom endpoint.
- Aggregating data from multiple downstream services or databases into a single response.
- Strongly typed contracts: the schema is the source of truth, and codegen for clients is excellent.
Where it bites:
- Caching. HTTP-layer caches are nearly useless because every query is a POST with a unique body. You build caching inside the application layer (DataLoader, persisted queries, response cache by query hash).
- Authorization. Field-level access control is the right model but it's more complex than per-route checks.
- N+1 queries. The natural way to write resolvers leads to one DB call per item; you need DataLoader or batched resolvers from day one.
- Rate limiting and abuse. A single query can fan out into thousands of resolvers — query complexity analysis is non-optional in public-facing GraphQL.
Reach for it when you have many client surfaces with different shapes of need, you want one schema across all of them, and you're prepared to invest in caching and complexity controls.
gRPC: the right answer between services
gRPC is RPC for the modern era. It runs over HTTP/2, uses Protocol Buffers for binary serialization, generates client and server stubs in a dozen languages, and supports four call modes (unary, server-streaming, client-streaming, bi-di streaming).
service UserService {
rpc GetUser (GetUserRequest) returns (User);
rpc StreamUserEvents (StreamRequest) returns (stream UserEvent);
}
What it's great at:
- Internal service-to-service calls where you control both client and server.
- Performance-sensitive paths — Protobuf is small and fast to parse, HTTP/2 multiplexes multiple in-flight calls on one connection.
- Strongly typed contracts. The
.protofile is the contract; you cannot accidentally drift. - Streaming. Server push, telemetry, chat, live data — the streaming modes make these easy.
Where it bites:
- Browser support is awkward. You need gRPC-Web (a translating proxy) to use it from a browser, which gives up some of the niceness.
- Debuggability. You can't curl a gRPC endpoint as easily; tooling exists (grpcurl) but it's another thing to install.
- Protobuf's schema evolution rules are strict. Add a field with the wrong number, lose data forever. Train your team.
Reach for it when the API is internal, performance matters, you want machine-checked contracts, or you need streaming.
A real comparison
Here's what each looks like for the same operation — fetching a user's recent posts:
# REST: maybe 1 round trip, often 2-3 to get related data
GET /users/42 → { id, name, ... }
GET /users/42/posts?limit=5 → [ { id, title, createdAt }, ... ]
# GraphQL: 1 round trip, exactly the fields needed
POST /graphql
{ user(id:42) { name posts(last:5) { title createdAt } } }
# gRPC: 1 round trip, binary encoding, HTTP/2 multiplexed
GetUser({id:42}) → User { name, posts: [...] }
Latency-wise, gRPC tends to win on raw numbers (binary, HTTP/2). GraphQL wins on round trips and over-fetch. REST wins on tooling and operational simplicity. The right choice depends on what's currently bleeding.
How to actually decide
A short decision flow that works in real teams:
- Is this an internal service-to-service call where you control both ends? Default to gRPC.
- Is it consumed by many client shapes (mobile + web + partner) you can't control? GraphQL is worth the investment.
- Is it a public API, or a small internal one with one or two consumers? REST. The tooling, caching, and operational simplicity will pay you back forever.
- Streaming or bi-directional? gRPC, or fall back to WebSockets / Server-Sent Events.
It's also fine — and common — to mix: REST/GraphQL at the edge, gRPC between services, all under the same observability stack.
Three rules, regardless of style
No matter which API style you pick:
- Version your contracts. REST: URL or header. GraphQL: schema evolution rules. gRPC: protobuf field numbers. Backwards-incompatible changes break clients you can't see.
- Idempotency for writes. Networks retry. If two identical create requests hit your server, you should never end up with two records. (Worth its own post.)
- Authentication at the edge, authorization at the resource. Don't bury auth checks deep in resolver code where they can be bypassed.
What to read next
If you're working on the design end of these decisions, system design basics covers the building blocks underneath — load balancers, caches, queues — that all three protocols sit on top of. The HLD writeups walk through entire systems where these choices show up in practice.
Frequently asked questions
Is GraphQL just better than REST?
No. GraphQL is genuinely better when you have many clients with different shapes of need (mobile + web + partners), and worse when you have a small number of well-known consumers. Don't pick it because it's newer.
When does gRPC actually pay off?
Internal service-to-service traffic where you control both ends, performance matters, and you want strict typed contracts. Streaming use cases (chat, telemetry, live updates) are gRPC's other sweet spot.
Can I mix them in one system?
Yes — and most large systems do. REST or GraphQL at the edge for clients, gRPC between services internally. Treat them as tools in a kit, not religions.
Read next
Apr 19, 2026 · 6 min read
API Versioning Strategies: URL, Header, and the Trade-offs Nobody Tells You
URL versioning, header versioning, content negotiation, and 'no versioning at all' — what each costs, what each gets you, and how to pick a strategy you won't regret in three years.
Apr 19, 2026 · 6 min read
Event-Driven Architecture: When Events Pay Off (and When They Don't)
What event-driven really means, the patterns that work — events vs commands, choreography vs orchestration, sagas, outbox — and the failure modes nobody warns you about.
Apr 19, 2026 · 6 min read
Idempotency in APIs: Why It Matters and How to Actually Implement It
Networks retry. Idempotency is what keeps a single user click from creating two charges. A practical guide to designing idempotent APIs without painting yourself into a corner.