Skip to content
Backend

gRPC vs REST: Contracts at the Boundary, Not Benchmarks

What gRPC actually is (three technologies in a trench coat), the honest performance paragraph, where each protocol is simply correct, and the hybrid edge-REST/interior-gRPC topology mature systems converge on.

4 min read Updated Sep 4, 2026
gRPC vs REST: Contracts at the Boundary, Not Benchmarks

gRPC arrives in most companies the same way: a new service boundary appears, someone benchmarks JSON serialization, someone else has fond memories from a previous job, and suddenly there's a proto/ directory and a holy war. Having shipped both extensively — REST for every public surface, gRPC between internal services where it earned the slot — here's the comparison that matters in practice, which is mostly not about performance.

Proto contracts and HTTP/2 streams versus the reach and simplicity of REST

What gRPC actually is, demystified

Three technologies in a trench coat: Protocol Buffers (a typed schema language + compact binary serialization), HTTP/2 transport (multiplexed streams over one connection), and code generation (client and server stubs in any language from the same .proto). The magic people attribute to "gRPC is fast" is mostly the third item wearing the first two as a costume:

service OrderService {
  rpc GetOrder (GetOrderRequest) returns (Order);
  rpc WatchOrderEvents (WatchRequest) returns (stream OrderEvent);  // server streaming!
}

message Order {
  string id = 1;
  int64 total_minor = 2;        // field numbers = wire contract, names are free
  OrderStatus status = 3;
  reserved 4;                    // deleted fields stay reserved forever
}

Run protoc and every team gets a typed client that can't misspell a field, can't send a string where an int64 goes, and can't miss a breaking change — it's a compile error. That's the real product: the contract is enforced by tooling instead of documentation. The additive-evolution discipline REST teams must adopt by convention, protobuf imposes structurally (field numbers, unknown-field tolerance, reserved).

The honest performance paragraph

Yes: binary framing beats JSON parsing, HTTP/2 multiplexing beats connection churn, and streaming beats polling. For chatty service-to-service traffic with small messages at high rates, the difference is real — think 2–10× on serialization-bound paths. But for the typical CRUD-over-network call, latency lives in the database and the business logic, not the encoding, and switching protocols to shave 3ms off a 90ms call is résumé-driven optimization. Choose gRPC for the contracts and streaming; accept the performance as a pleasant bonus, not the thesis.

Where each one is simply correct

gRPC's home turf: internal service-to-service APIs in polyglot environments (codegen across languages is where it shines brightest), streaming workloads (server-push telemetry, long-lived watches — native stream semantics instead of WebSocket scaffolding), and high-rate low-latency internal calls. The Kubernetes-era toolchain (health probes, load-balancer awareness, service mesh integration) treats it as a first-class citizen — with one classic gotcha: gRPC's long-lived HTTP/2 connections defeat naive L4 load balancing; you need client-side LB or a mesh/proxy doing per-request balancing, or one lucky pod gets all the traffic.

REST's unassailable ground: anything a browser, webhook, third-party integrator or curl command touches. The reach story is decisive — every language, every debugging proxy, every caching layer (HTTP caching is a REST superpower gRPC simply lacks), every junior developer's first hour. Public APIs are REST (or REST-shaped) not by fashion but because your consumers' convenience is the product, and nobody wants to run protoc to try your API. Browser-to-server gRPC exists (gRPC-Web) but needs a proxy and loses features; it's rarely worth it over a clean REST/JSON edge.

The decision table, and the hybrid that usually wins

Public API, partners, webhooks, browsers      → REST + OpenAPI, no debate
Internal service-to-service, 2+ languages     → gRPC earns its tooling cost
Internal, but it's 3 Laravel services         → REST/JSON is fine; skip the ceremony
Streaming (watch/subscribe/telemetry)         → gRPC internally; WebSockets/SSE at edge
Extreme-throughput internal paths             → gRPC, measured not assumed

The standard mature topology:
  edge: REST/JSON (public, cacheable, curl-able)
  interior: gRPC where contracts + streaming pay rent
  …and a BFF translating between them.

Two adoption warnings from the field. First, gRPC is an ecosystem commitment — proto repos, codegen in CI, new debugging muscle memory (grpcurl replaces curl), interceptors replacing middleware; budget the on-ramp honestly, because half-adopted gRPC (protos copy-pasted between repos, no CI codegen) delivers the costs without the contract guarantees. Second, don't let the protocol decide your boundaries — the hard part of service design is where the seams go and what the contracts say; whether those contracts travel as protobuf or JSON is the easy 20%. Teams that get the seams right thrive on either protocol. Teams that get them wrong discover that binary-encoding a bad boundary just makes the mistake faster.

Standing up your first gRPC service, or refereeing the holy war internally? An architecture hour settles it with your actual traffic numbers instead of vibes.

Keep reading

Related articles

Backend 5 min read

The Caching Stack: From Browser to Buffer Pool

Five layers walked top to bottom — browser headers, CDN edge with its famous incident, Redis with a job description, database-adjacent options — plus the staleness grid that makes TTLs a product decision.

Backend 4 min read

GraphQL: An Honest Take After the Hype Cycle

The specific problem it solves brilliantly, the five bills itemized — resolver N+1s, forfeited HTTP caching, query-surface DoS, field-level auth, the toolchain — and the BFF alternative most teams actually need.