REST and GraphQL both send text (JSON) over HTTP and figure out the shape of things as they go. gRPC bets on the opposite of both choices — a binary format and a contract fixed in advance — for a very specific reason: service-to-service calls where every millisecond and every byte is being counted.
3 min read
REST and GraphQL are built for a client (often a browser or mobile app) talking to a server it doesn't fully control, over a connection that's often slow and unreliable. gRPC targets a different, common situation: one internal service calling another, inside the same data center or cluster, at high volume, where both ends are code the same organization controls and can update together. That different starting assumption justifies design choices REST and GraphQL wouldn't make.
Instead of JSON's flexible, self-describing text, gRPC uses Protocol Buffers (protobuf) — data serialized as compact binary, based on a schema defined up front:
message User {
int32 id = 1;
string name = 2;
string email = 3;
}
service UserService {
rpc GetUser (GetUserRequest) returns (User);
}The numbers (= 1, = 2, = 3) aren't cosmetic — they're the actual
field identifiers encoded in the binary payload instead of the field
names, which is a large part of why protobuf messages are so much smaller
than the equivalent JSON: no repeated field-name strings in every single
message, no whitespace, no quotes.
Smaller payloads and a binary format that doesn't need to be parsed character-by-character both directly reduce latency and CPU cost per call — genuinely significant at the volume internal service meshes operate at (thousands of calls per second between services), even though a human opening the raw bytes in a text editor sees gibberish instead of readable JSON. The trade-off is intentional: gRPC assumes tooling (generated client code, protobuf decoders) handles the format, not a person reading raw wire traffic directly the way they might casually inspect a REST response.
The .proto file schema is compiled into actual client and server code in
whatever language each side uses — a Go service and a Python service can
call each other via gRPC, each with fully-typed, generated client code for
the other's API, without either side hand-writing an HTTP client or
manually keeping request/response shapes in sync. This is a stronger,
more automatically-enforced contract than REST's typical
documentation-plus-hope approach, closer in spirit to what lesson 9's
contract-first design tries to achieve for REST via separate tooling.
Built on HTTP/2, gRPC natively supports streaming — a client or server (or both) can send a continuous sequence of messages over one long-lived connection, rather than a single request/response pair. This fits use cases like a live feed of sensor readings, or a large dataset processed incrementally, in ways that don't map naturally onto REST's request-then-single-response model at all.
Browsers don't have native gRPC support the way they have native fetch()
for JSON over HTTP — using gRPC from a browser typically requires an extra
proxy layer (like gRPC-Web) translating between what a browser can actually
send and gRPC's native binary/HTTP2 requirements. This overhead is exactly
why gRPC dominates internal service-to-service communication but rarely
shows up as the direct interface a public-facing web or mobile client talks
to — that role usually stays with REST or GraphQL, discussed together in
lesson 7.
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. What situation is gRPC's design specifically optimized for?
2. Why are Protocol Buffer messages typically smaller than the equivalent JSON?
3. Why is gRPC rarely used as the direct interface a browser client calls?
API Design