gRPC vs REST: Web Menu vs Phone-Line Contract (and When to Use Which)
REST and gRPC are two ways to build an API. REST is ordering à la carte off a web menu — a plain HTTP request to a resource, JSON back, anyone can call it. gRPC is a dedicated phone line with a strict contract — a Protobuf schema, generated typed stubs, compact binary over HTTP/2 with streaming. Here's how they actually differ on speed, payload, reach, and tooling — with code — and a clear rule for which to reach for.
Two services need to talk. You reach for an API — and immediately hit the first real decision: REST or gRPC? Pick wrong and you pay for it later in either latency or developer friction. The good news: once you see what each one actually is, the choice is usually obvious.
The one-line mental model:
- REST is ordering à la carte off a web menu — you send a plain HTTP request to a resource, you get JSON back. Anyone can read the menu and order.
- gRPC is a dedicated phone line with a strict contract — both sides agree on a schema up front, you call typed methods, and compact binary flies over the wire.
REST: order à la carte over HTTP
REST treats everything as a resource addressed by a URL, manipulated with HTTP verbs (GET, POST, PUT, DELETE). The payload is almost always JSON. There's no contract to share ahead of time — the "menu" is just the URLs, and any client that speaks HTTP can order.
GET /orders/42 HTTP/1.1
Host: api.shop.dev
Accept: application/json
200 OK
{
"id": 42,
"item": "flat white",
"status": "ready"
}
That's the whole appeal: it's readable, universal, and zero-setup. A browser, curl, a mobile app, a partner's backend — they can all hit it without generating any code. And because GETs are plain HTTP, they're cacheable by browsers, CDNs, and proxies for free.
gRPC: a strict contract on a fast line
gRPC flips the model. Instead of loose URLs and JSON, you define the contract first in a Protobuf (.proto) schema — the methods, the request types, the response types:
syntax = "proto3";
service Orders {
rpc Get(OrderId) returns (Order);
}
message OrderId { int32 id = 1; }
message Order {
int32 id = 1;
string item = 2;
string status = 3;
}
You run that schema through the gRPC compiler and it generates typed client and server stubs for your language. Calling a remote method then looks like calling a local function:
# client — the stub is generated from the .proto
import grpc, orders_pb2, orders_pb2_grpc
with grpc.insecure_channel("api.shop.dev:50051") as channel:
stub = orders_pb2_grpc.OrdersStub(channel)
order = stub.Get(orders_pb2.OrderId(id=42)) # typed call, binary on the wire
print(order.item, order.status)
On the wire it's compact binary over HTTP/2 — multiplexed, header-compressed, and with native support for streaming in either direction (server-stream, client-stream, or bidirectional).
The real differences
Speed & payload
REST ships text JSON over HTTP/1.1, one request/response at a time. gRPC ships small binary (Protobuf) over HTTP/2, multiplexed, with real streaming. For chatty, internal service-to-service traffic, gRPC is noticeably faster and lighter — smaller messages, fewer connections, no JSON parse on the hot path.
Reach & tooling
This is where REST wins. REST runs anywhere — no shared contract, no code generation, and browsers call it directly. gRPC needs the .proto, generated stubs, and HTTP/2, and a browser can't call gRPC directly — you need a grpc-web proxy (e.g. Envoy) in front. That setup cost buys you speed and type-safety, but it's a real cost.
| REST | gRPC | |
|---|---|---|
| Payload | JSON (text) | Protobuf (binary) |
| Transport | HTTP/1.1 | HTTP/2 (multiplexed) |
| Contract | None required | `.proto`, generated stubs |
| Streaming | Limited | First-class, bidirectional |
| Browser | Direct | Needs grpc-web proxy |
| Caching | Free (HTTP) | Manual |
| Human-readable | Yes | No (binary) |
Which should you reach for?
- Reach for REST when you're building a public API, serving browser clients, want simplicity, or benefit from HTTP caching. It's the right default for anything external or anything you want others to consume with zero onboarding.
- Reach for gRPC when it's internal microservice-to-microservice traffic, you need low latency, you want streaming, or you want strict typed contracts shared across many languages.
And the part most "vs" arguments miss: most real systems use both. REST (or GraphQL) at the edge for browsers and partners; gRPC between your internal services where speed and contracts matter. It's not a religious war — it's picking the right tool for each hop.
Want the 90-second visual version? Watch the reel.