Developer Productivity & Platform EngineeringPlaybook3 min readUpdated September 2026

REST, GraphQL, or gRPC: Choosing by Workload

A team adopts GraphQL because it's popular, builds a resolver for every nested field, and ends up querying the database dozens of times per request because nothing batches those resolver calls together. A different team picks gRPC for a public API and discovers browsers can't call it directly without an extra proxy layer.

None of these protocols is wrong. They solve different problems, and picking one because it's trending instead of matching it to the actual workload is where the trouble starts.

REST: Boring, Cacheable, and Still Right for Most Public APIs

REST's biggest advantage is how well understood it is: every client library, every proxy, every piece of infrastructure already knows how to cache a GET request and respect an HTTP status code. Versioning by URL path is simple to reason about, and debugging with a browser or curl needs no special tooling.

Its weak point is shape mismatch. A mobile client that only needs three fields still gets the whole object back, and a client that needs data from three different resources has to make three round trips or accept a bespoke aggregation endpoint.

GraphQL: Solves Over-Fetching, Creates a New N Plus One Problem

GraphQL lets a client ask for exactly the fields it needs across multiple resources in one request, which is genuinely useful for a mobile app pulling data from several backend services at once. The tradeoff shows up on the server: a naive resolver-per-field implementation triggers a separate database query for every item in a list, the classic N plus one pattern, unless you add batching, typically a dataloader-style cache that groups those lookups together within a single request.

A single GraphQL endpoint also makes HTTP-level caching harder, since every request is technically a POST to the same URL; you end up caching at the application layer instead.

gRPC: Fast Internal Calls, Not a Browser-Facing Protocol

gRPC's binary protobuf encoding and HTTP/2 streaming make it a strong fit for service-to-service calls inside your own infrastructure, where you control both ends and latency actually matters. Contracts are strongly typed and generated from a schema, which catches a lot of integration mistakes at compile time instead of in production.

What it isn't is a drop-in public API protocol. A browser can't call gRPC directly without a gRPC-Web proxy translating the calls, and debugging a binary payload with plain tooling is harder than reading a JSON body.

How do you choose between REST, GraphQL, and gRPC by workload?

  • A public API with client needs you can't fully predict: REST with solid pagination and clear versioning.
  • One client, mobile or web, pulling nested data from many backend services: GraphQL with resolver batching in place from day one.
  • Calls between services you control, where latency and type safety matter more than human readability: gRPC.
  • You don't have to pick just one for the whole company. REST at the public edge and gRPC between your own services is a normal, common combination.

What mistakes should you avoid when migrating between protocols?

Rewriting an entire working REST API into GraphQL in one pass, instead of adding a GraphQL gateway in front of the existing services and migrating client by client, turns a manageable project into a risky one. Adopting gRPC for a public-facing API before checking whether your actual client base can call it is a similar trap. And letting a GraphQL schema grow without any deprecation discipline means every field you ever shipped is effectively supported forever, because nothing ever tells clients to stop using it.

Versioning Each Protocol Without Breaking Clients

REST typically versions by URL path or a header, giving old clients a stable target to keep calling while new ones move on; the cost is maintaining more than one version of the same endpoint in parallel. GraphQL takes a different approach: fields get marked deprecated with a note on what replaces them, and clients migrate field by field instead of the whole schema jumping versions at once, which only works if someone actually tracks which clients still call the deprecated fields.

gRPC leans on its schema definition for this: adding a new field is safe, but removing or renaming one breaks every client still compiled against the old contract, so changes there need the same discipline as a public REST version bump, just enforced by the compiler instead of by convention.

Executive Capability Standard

What Good Looks Like

Good API protocol choices mean each interface matches its actual caller, a browser-friendly protocol at the public edge and a fast typed protocol between your own services, instead of one protocol stretched to cover every use case.

Building The Capability (5-Stage Skill Ladder)

1. Learn:List every API and internal service call in your system today and note which protocol each one uses and why.
2. Do Manually:Manually add resolver batching to the one or two GraphQL fields generating the most database queries per request.
3. Delegate:Give one engineer ownership of the API style guide so new endpoints follow a consistent protocol decision instead of whoever built it last.
4. Automate:Add automated query cost or complexity limits to your GraphQL layer so one expensive nested query can't overload the database.
5. Buy:Bring in an API specialist to review your highest-traffic endpoints before a major protocol migration, not after it's already underway.

How to Get Started

Frequently Asked Questions

Can I use more than one of these protocols at the same company?

Yes, and most companies past a certain size do. A common pattern is REST or GraphQL at the public edge, where broad client compatibility matters, and gRPC between internal services, where you control both ends and want speed and strong typing. Picking per workload beats picking one protocol for everything.

Why does GraphQL need a dataloader style batching pattern?

Because a naive resolver runs independently for every field on every item in a list, which turns one request for a list of one hundred items into potentially one hundred separate database lookups. A batching layer collects those lookups within a single request and runs them together, avoiding the N plus one pattern.

Is gRPC ever the right choice for a public API?

It can be, for API consumers who are themselves backend services and can generate a typed client, such as partner integrations. For a public API that browsers or third-party developers will call directly, REST or GraphQL is almost always the better starting point unless you're prepared to maintain a gRPC-Web proxy layer.

About the numbers

This guide doesn't quote a sourced benchmark. Figures in it are estimates or general guidance, so check them against your own numbers.

Related Guides