gRPC is Google's RPC implementation: typed, fast, multi-language service-to-service calls over HTTP/2 with Protobuf binary serialization . It works in three steps:
- Write a
.protofile: define your messages and service methods once. This is the contract : language-agnostic and source-controlled. - Run
protoc: the compiler reads the.protoand generates typed client and server stubs in any language: Go, Java, Python, Node, Rust. - Call it like a function: your code calls the generated stub. It serializes arguments to binary, sends them over HTTP/2, and deserializes the response, transparently.
// What you write (any language, this is Node)
bookService.checkoutBook({ id: 1 }, (err, book) => {
// ...use the returned book
});
// → looks like a local function call
// → under the hood: Protobuf binary → HTTP/2 → server → Protobuf binary back Error handling is different
gRPC abstracts HTTP away completely. You never return an HTTP 404; instead, gRPC has its own set
of 16 standard status codes (NOT_FOUND, FAILED_PRECONDITION,
UNAVAILABLE) that the generated stubs surface as errors in your language.
// Server-side: signal failure with a gRPC status code, not an HTTP one
checkoutBook: (call, callback) => {
const book = books.find((b) => b.id === call.request.id);
if (!book?.available) {
return callback({ code: grpc.status.FAILED_PRECONDITION, message: "Not available" });
}
book.available = false;
callback(null, book);
}, Built-in streaming
Streaming is a first-class primitive in the contract, not a bolt-on. You declare the mode right in
the .proto by marking the request and/or response with stream, and
protoc generates a stub whose shape matches: a method that returns a single value, or
one you read from (and write to) like an ordinary stream. Because it all rides over a single
long-lived HTTP/2 connection, many messages multiplex over one socket with no per-message
handshake.
// Example .proto file, explored further in the next section
service BookService {
// unary: one request, one response
rpc CheckoutBook(BookRequest) returns (Book);
// server streaming: mark the response with 'stream'
rpc ListBooks(Filter) returns (stream Book);
// client streaming: mark the request
rpc UploadCatalog(stream Book) returns (UploadSummary);
// bidirectional: mark both
rpc Chat(stream Message) returns (stream Message);
} REST has no equivalent in the contract. You reach for a separate mechanism like SSE, WebSockets, or chunked responses and wire it up by hand. In gRPC the four modes are just method signatures:
| Mode | Flow | Use case |
|---|---|---|
| Unary | 1 req → 1 res | Same as a REST call. The default. |
| Server streaming | 1 req → many res | Client asks once; server pushes a stream. Like SSE but typed and binary. |
| Client streaming | many req → 1 res | Client streams data (file chunks); server replies once. |
| Bidirectional | many req → many res | Both sides stream independently. Voice, games, live collaboration. |
When to use gRPC
Use it when…
- Internal microservices: same data center, high volume, performance matters. Google, Kubernetes, and Uber all use gRPC internally.
- Multi-language teams: one
.protogenerates typed stubs for every team's language. - You need streaming: IoT telemetry, video feeds, live analytics.
- You want strict contracts: a breaking change is a compile error, not a runtime surprise.
Skip it when…
- Public APIs (usually): external developers expect REST + JSON. (Tools like gRPC-Gateway can translate a gRPC service into a REST API automatically.)
- Browser clients: browsers can't speak raw gRPC. JavaScript APIs like
fetchdon't expose HTTP trailers during streaming, which gRPC requires. You need a proxy layer like gRPC-Web or ConnectRPC. - Simple CRUD services: the
protoctoolchain overhead isn't worth it for a small service. - You need readable traffic: binary Protobuf is opaque to
curland browser DevTools.
Check your understanding
Signaling not-found in gRPC
In REST you return HTTP 404 when a resource isn't found. How do you signal the same failure in gRPC?