βš–οΈ gRPC vs REST

Not "which is better" β€” which fits where. gRPC and REST/JSON make opposite trades on typing, transport, and reach. Pick per boundary.

They Optimize for Different Callers

The short version: gRPC optimizes for machines calling machines β€” typed, compact, streaming, low-overhead. REST/JSON optimizes for reach and legibility β€” any browser, any curl, human-readable, cache-friendly. Most systems use both, at different boundaries.

Head to Head

DimensiongRPCREST / JSON
PayloadProtobuf β€” compact binaryJSON β€” verbose text
TransportHTTP/2 requiredAny HTTP (1.1 fine)
Contract.proto, enforced, codegen'dOpenAPI β€” optional, often drifts
StreamingFirst-class, all 4 shapesAwkward (SSE / chunked / polling)
Browser supportNeeds gRPC-Web + proxyNative, universal
Human-debuggableNo β€” binary, needs toolingYes β€” curl, browser, logs
Caching (intermediaries)Not really (opaque POSTs)Strong β€” HTTP caching semantics
Tooling maturityGreat codegen; weaker ecosystem breadthEnormous, ubiquitous
Latency / CPU overheadLow (binary, one conn, header compression)Higher (text parse, headers)
Schema evolutionStrong, field-number basedConvention-based, easy to break

The Same Call, Both Ways

// gRPC: contract-first, binary on the wire
service UserService {
  rpc GetUser(GetUserRequest) returns (User);
}
// REST: URL + verb + JSON body
// GET /v1/users/u_123
{ "user_id": "u_123", "display_name": "Ada", "email": "ada@x.dev" }

The gRPC side ships a generated, type-checked client in every language. The REST side you can hit from a browser address bar. That's the trade in one screen.

Choose gRPC When…

  • Internal service-to-service traffic on your own network β€” the dominant sweet spot.
  • You want streaming (feeds, uploads, chat, live sync) without bolting on SSE/WebSockets.
  • Polyglot services that need one enforced contract and generated clients.
  • Latency and payload size matter (high QPS, mobile bandwidth, fan-out trees with deadline propagation).
  • You want rich, typed errors and per-call metadata out of the box.

Choose REST / JSON When…

  • Public or third-party APIs β€” consumers expect curl-able JSON and universal tooling.
  • Browser is a first-class client and you don't want a gRPC-Web proxy in the path.
  • You lean on HTTP caching / CDNs β€” GET + ETag + Cache-Control.
  • Simple CRUD where human-readability and debuggability beat raw efficiency.
  • You need to be hittable from anywhere with zero client generation.

The Browser Problem & gRPC-Web

Browsers can't speak raw gRPC β€” JavaScript has no access to HTTP/2 trailers or frame-level control. gRPC-Web is a browser-compatible dialect that a proxy (Envoy, or a built-in gateway) translates to real gRPC behind the scenes.

flowchart LR B["Browser
gRPC-Web client"] -->|gRPC-Web over HTTP/1.1 or 2| P["Proxy
(Envoy / gateway)"] P -->|native gRPC / HTTP/2| S["gRPC service"]
Caveat: gRPC-Web supports unary and server streaming, but not client-streaming or bidi from the browser. For true bidirectional in a browser you're back to WebSockets.

You Don't Have to Choose Globally

Real-world pattern: gRPC inside the mesh, REST at the edge. Services talk gRPC to each other; a gateway exposes a REST/JSON facade to browsers and partners. Tools like gRPC-Gateway / transcoding generate the REST layer straight from the same .proto, so you write the contract once and get both. The status-code mapping is what makes that translation clean.