Every API design starts with a blank document and one uncomfortable question: how will clients talk to this thing? Most teams write "REST" at the top because that is what the last project used. That is not a decision, it is a habit. REST, GraphQL, and gRPC each solve a different problem, and the cost of a mismatch tends to arrive late — as over-fetching on mobile screens, as a schema that drifts away from the code, or as a streaming feature that cannot ship without replacing the transport layer.
What Each Style Actually Optimizes For
REST models your system as resources addressed by URLs and manipulated with HTTP verbs. Its real superpower is not JSON — it is the surrounding infrastructure: status codes, caching headers, CDNs, proxies, and a uniform interface every developer already understands. If you can describe your domain as nouns that clients read and write, REST fits with little friction.
GraphQL exposes a single endpoint and a typed schema, and lets the client choose which fields it wants. That solves two chronic REST problems at once: over-fetching (the mobile screen needs three of your forty fields) and under-fetching (assembling one screen from five endpoints). The trade is that the server must now reason about query cost, depth, and authorization per field.
gRPC is contract-first. You define services and messages in Protocol Buffers, generate client and server code, and speak a compact binary format over HTTP/2. It is built for service-to-service traffic: typed contracts, small payloads, and four kinds of streaming out of the box.
Performance Is Not One Number
Payloads and parsing
Protobuf encodes to a much smaller wire format than equivalent JSON and needs far less parsing on both ends, which matters when you make thousands of calls per second between services. For a handful of calls per user session, JSON is rarely the bottleneck — your database query is. Optimizing serialization before measuring the slow part is the classic way to polish the wrong component.
Round trips
A REST screen composed of five endpoints costs five round trips unless you add a backend-for-frontend or a compound endpoint. GraphQL collapses that into one request, which helps most on high-latency mobile networks. gRPC helps when you need streaming: server-push, client-push, or a long-lived bidirectional channel. You can stream over REST too, via server-sent events or WebSockets, but you assemble it yourself.
Caching
REST gets HTTP caching almost for free: ETags, Cache-Control, and CDN edges serving responses without touching your origin. GraphQL POSTs are not cacheable by default; you need persisted queries, automatic persisted queries, or a client-side normalized cache. gRPC has no shared HTTP cache story, which is usually fine inside a datacenter and awkward on the public web.
Flexibility and Developer Experience
Flexibility cuts both ways. GraphQL gives clients enormous freedom, and that freedom lands on your server as work: query depth limits, cost analysis, N+1 queries against your database, and field-level authorization. Plan for that before you commit.
REST is flexible in another sense. Any HTTP client in any language works, curl works, and the OpenAPI tooling ecosystem is mature. The cost shows up later, as endpoints multiply and clients start chaining calls the API never intended.
gRPC gives you generated typed clients — a genuine pleasure in polyglot teams — but browsers need gRPC-Web or a proxy, and reading binary frames requires proper tooling. For a public API consumed by strangers, that friction is an adoption tax.
Where Each Style Wins
- Public or partner API, broad audience: REST. Familiar, cacheable, curl-friendly, documented with OpenAPI.
- One product, several client types (iOS, Android, web) needing different slices of the same data: GraphQL, often with a backend-for-frontend per client.
- Internal microservices at high call volume: gRPC. Typed contracts and cheap serialization pay off immediately.
- Realtime and streaming: gRPC between services, WebSockets or SSE for browser-facing features.
- Simple CRUD with a single client: REST. GraphQL adds machinery you will not use.
- Mixed estate: gRPC internally, REST or GraphQL at the edge. Common and perfectly reasonable.
A Short Decision Checklist
- Who consumes it? Strangers on the internet, three internal services, or your own app? Public APIs rarely justify protobuf.
- What is the network? Datacenter latency and mobile latency reward different choices.
- Do you need streaming? If yes, weigh gRPC against WebSockets honestly rather than by reputation.
- How much tooling can you maintain? GraphQL means query limits, dataloaders, and cache configuration.
- What does the team already operate well? A well-run REST API beats a neglected gRPC one.
- Can you measure? Prototype the two finalists against your real data shape for a day before deciding.
Mistakes That Cost Teams Months
Adopting GraphQL for an API with one client and a flat data model. Exposing gRPC straight to browsers and then discovering the proxy tax. Treating REST as an excuse to skip a schema — generate OpenAPI from your server and check it in CI. Picking a style because a conference talk used it, without asking what constraints that team had.
You are also not married to a single style. A common shape is gRPC between services, a thin GraphQL or REST gateway for clients, and caching wherever it helps.
Choose Constraints, Then a Style
Write down three things before you pick: who calls your API, over what network, and what your team can operate on a bad Tuesday. If the answer is "the public internet, unknown clients, and a small team," REST is almost certainly right. If it is "our apps need different slices of one data graph and we can support a schema," GraphQL earns its complexity. If it is "dozens of internal services exchanging typed messages at volume," gRPC is the shortest path.
Then build a small vertical slice with real data — one endpoint, one client, one query that matters — and see how it feels to change it a week later. An afternoon of that teaches more than another comparison, including this one.


