REST vs GraphQL vs gRPC in 2026: An Architect's Decision Matrix

μž‘μ„±μž

μΉ΄ν…Œκ³ λ¦¬:

← ν”Όλ“œλ‘œ
DEV Community · Swetha Golla · 2026-07-22 개발(SW)

By Swetha Golla Β· ~6 min read Β· Senior Application Architect

πŸ”— This post has a live interactive version with a clickable decision tool β€” pick your boundary (public API, internal service-to-service, or mobile/bandwidth-constrained) and see the verdict β€” plus a round-trip diagram comparing REST, GraphQL, and gRPC: read it here

TL;DR

  • Public API? REST. Cacheable, curl-able, and your consumers already know it.
  • Service-to-service inside your own walls? gRPC. Contracts, streaming, and protobuf serialization that’s consistently ~3x faster than JSON (deserialization depends on payload size) β€” the perf and type-safety win. Not for a public API, though: browsers can’t open a native gRPC connection, so external consumers would need grpc-web plus a translating proxy β€” real infrastructure most third parties won’t build just to call you.
  • One backend feeding many divergent UIs? GraphQL β€” but only if you’ll staff the schema like a product.
  • Protocol choice is an organizational decision wearing a technical costume. Pick per boundary, not per company.

The Setup

Picture a mid-sized logistics platform with a driver mobile app, a dispatcher web console, 40+ internal services, and a partner API that third-party warehouses integrate against. The team is consolidating three generations of API styles into a deliberate standard, and every faction has a favorite. The wrong call gets baked into every integration for the next five years.

The mistake this team almost made β€” the mistake most teams make β€” is picking one winner. There isn’t one. There are three boundaries, and each has a correct answer.

Pick your boundary

The live version lets you click through each context. Here’s the same breakdown in prose.

Public-facing API β†’ REST

Your partners’ junior dev with curl and a Stripe-shaped mental model is your real user. REST wins on zero onboarding friction, HTTP caching that CDNs understand natively (ETags, Cache-Control β€” free infrastructure), and 20 years of tooling: gateways, rate limiters, OpenAPI codegen, Postman. Versioning is a solved, boring problem.

Watch out: resist the urge to expose GraphQL publicly “for flexibility.” You’d be handing anonymous strangers the ability to write expensive queries against your database. This isn’t hypothetical: Shopify and GitHub both run their public GraphQL APIs on query-cost rate limiting instead of simple per-request limits β€” Shopify prices every query in points calculated from the objects and connections it touches, before execution even starts. That’s the tax public GraphQL collects; you’ve effectively rebuilt REST’s simplicity, just with a cost engine instead of a request counter.

Internal service-to-service β†’ gRPC

Inside your own walls, you control both ends β€” so spend nothing on human-readability and everything on contracts and efficiency. Protobuf schemas give you compile-time breakage detection across teams, generated clients in every language, and bidirectional streaming for the workloads REST handles awkwardly (progress updates, event feeds). gRPC’s own published mobile benchmarks are more nuanced than the marketing pitch, and more credible for it: protobuf serialization is consistently about 3x faster than JSON regardless of message size, but deserialization depends on size β€” JSON is actually ~1.5x faster than protobuf for small messages under 1KB, while protobuf pulls ahead by about 2x for messages over 15KB. Gzip JSON and protobuf’s serialization edge grows past 5x, with deserialization roughly even at small sizes and protobuf ~3x faster at larger ones. HTTP/2 multiplexing and HPACK header compression add further gains, and the gap widens with message size and load.

Watch out: debuggability drops. You’ll want grpcurl, server reflection, and good interceptor-level logging from day one β€” “I can’t just read the payload in the proxy logs” is the #1 complaint from teams six months in. Also: “internal” means service-to-service. A browser can’t open a native gRPC connection β€” you’d need grpc-web plus a proxy that translates back to HTTP/2, which is real extra infrastructure, not a footnote.

Mobile / bandwidth-constrained β†’ GraphQL

This is where GraphQL genuinely earns its keep: a screen needs a user, their shipments, and each shipment’s latest status, on a flaky cellular link. In my illustrative repo POC (not a formal benchmark β€” see below), that’s 1 trip and 33% of REST’s bytes for one representative shape. On a 300ms-RTT connection, collapsing 5 trips into 1 is over a second of perceived latency back.

Watch out: the “N+1 problem” doesn’t disappear, it moves β€” off the client’s round-trips and onto your resolvers, where naive field resolution re-triggers a query per item unless you batch it (DataLoader et al.). And once anonymous or semi-trusted clients can shape their own queries, a single nested query can fan out into a database-melting number of calls. This is exactly why Shopify and GitHub don’t rate-limit their public GraphQL APIs by request count β€” they charge per query based on the objects and connections it touches, computed before execution. If you’re not willing to build (or buy) that same cost-accounting and resolver-batching machinery, don’t ship a general-purpose graph. A handful of purpose-built REST “screen endpoints” (BFF pattern) gets you 80% of the win at 20% of the operational cost.

The trade-off matrix

REST GraphQL gRPC Payload size / efficiency Full resources, over-fetching; N+1 trips for nested data Exact fields, one trip β€” still JSON text on the wire Binary protobuf β€” serialization ~3x faster than JSON at any size; deserialization favors JSON (~1.5x) under 1KB but protobuf (~2x) over 15KB; streaming built in Tooling / debuggability curl, browser, any proxy β€” everything speaks it Introspection + GraphiQL are excellent; errors hide inside 200 OK Opaque binary; needs grpcurl, reflection, protoc toolchain Caching support First-class: ETags, CDNs, browser cache β€” all free POST-everything defeats HTTP caching; needs persisted queries + client normalization Effectively DIY β€” but internal calls rarely need shared caches Consumer learning curve Near zero β€” the lingua franca of the web Real curve: query language, fragments, error semantics, client libs Generated clients feel like local calls; proto/build setup is a tax on outsiders

(In the table above, “wins” and “costs” are relative to each other β€” every cell is a real trade-off, not a free win.)

Numbers, not vibes

I built a small stdlib-Python POC (linked below) simulating one concrete request β€” “fetch a user, their 3 posts, and 2 comments per post” β€” across all three styles. This is a simplified illustrative demo, not a formal benchmark: one hand-picked payload shape, no compression, no real network, no server-side processing cost modeled. Treat the percentages as “here’s the mechanism,” not “here’s the industry number.”

Protocol   Round-trips   ~Bytes    vs REST
REST                 5    3,105       100%
GraphQL              1    1,017        33%
gRPC                 1      629        20%

Enter fullscreen mode Exit fullscreen mode

Yes, gzip narrows the byte gap on JSON. It does nothing for the four extra round-trips β€” and on mobile, round-trips are the whole game.

What gRPC’s own benchmarks show: gRPC’s official “Mobile Benchmarks” post is more nuanced than the usual “binary always wins” pitch β€” which is exactly why it’s worth citing. Protobuf serialization is consistently about 3x faster than JSON, independent of message size. Deserialization is size-dependent: JSON is actually ~1.5x faster than protobuf for small messages under 1KB, and only once messages exceed 15KB does protobuf take a clear ~2x deserialization lead. Gzip-compress the JSON and protobuf’s serialization edge widens past 5x, while deserialization is roughly a wash at small sizes and swings to protobuf (~3x) at larger ones. Translation: for tiny payloads and small internal RPCs, plain JSON deserialization can be perfectly fine β€” even faster β€” and the case for gRPC strengthens as messages and scale grow, which lines up directionally with what the toy POC above shows.

Rule of thumb: REST when strangers call you. gRPC when you call yourself. GraphQL when many screens call one graph β€” and only if you’ll staff the schema like a product.

Where I actually stand

Watching these decisions age, the pattern is clear: most public APIs do not need GraphQL, and most that adopted it are quietly paying a maintenance tax β€” cost limiters, persisted queries, resolver performance archaeology β€” to solve problems REST never had. Meanwhile gRPC is criminally underused internally: teams keep hand-writing JSON clients between their own services because “REST is simpler,” then spend that saved week per quarter debugging contract drift a .proto file would have caught at compile time. The senior move isn’t picking the sophisticated option. It’s matching the protocol to the trust boundary and refusing to relitigate it every sprint.

See the working example β†’ Runnable POC behind the round-trip/byte demo above; published-benchmark figures are cited separately. Disagree with the matrix? Good β€” tell me which cell and why.

Sources & Further Reading

μ›λ¬Έμ—μ„œ 계속 β†—

μ½”λ©˜νŠΈ

λ‹΅κΈ€ 남기기

이메일 μ£Όμ†ŒλŠ” κ³΅κ°œλ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. ν•„μˆ˜ ν•„λ“œλŠ” *둜 ν‘œμ‹œλ©λ‹ˆλ‹€