Defining the Contract in .proto Files
The foundation of gRPC is the Protocol Buffer (Protobuf) schema — a .proto file that defines the data structures (called messages) and the service interface (the available RPC methods). This file is the single source of truth shared between client and server.
// sensor.proto
syntax = "proto3";
package sensor;
// The data structure for a sensor reading
message SensorReading {
float temperature = 1; // field number 1
int32 sensor_id = 2; // field number 2
int64 timestamp = 3; // field number 3
}
// The data structure for the server's response
message RecordResponse {
bool success = 1;
string message = 2;
}
// The service definition — what RPCs are available
service SensorService {
rpc RecordReading (SensorReading) returns (RecordResponse);
rpc StreamReadings (stream SensorReading) returns (RecordResponse);
}
The = 1, = 2, = 3 are field numbers — unique identifiers for each field in the binary encoding. In the serialized binary, field names are never transmitted. Instead, each value is prefixed with its field number and wire type (a few bits indicating the data type). The field temperature = 22.5 becomes roughly 5 bytes: 1 byte for the field tag (field number 1, wire type float) and 4 bytes for the IEEE 754 float value. Compare to JSON's "temperature":22.5 which is 18 bytes.
The protoc compiler takes this .proto file and generates client and server code in your target language (Go, Python, Java, C++, Node.js, and many more). The generated code handles all serialization and deserialization automatically.
The Four gRPC Communication Patterns
Because gRPC is built on HTTP/2, it natively supports streaming in both directions. This gives it four distinct communication patterns:
| Pattern | Client sends | Server sends | Use case |
|---|---|---|---|
| Unary | Single request | Single response | Standard API call (fetch user by ID) |
| Server Streaming | Single request | Stream of responses | Live stock prices, log tailing |
| Client Streaming | Stream of requests | Single response | Uploading a large file in chunks |
| Bidirectional Streaming | Stream of requests | Stream of responses | Real-time chat, collaborative editing |
service DataService {
// Unary
rpc GetUser (UserRequest) returns (User);
// Server streaming — note "stream" before the return type
rpc WatchPrices (TickerRequest) returns (stream PriceUpdate);
// Client streaming — note "stream" before the request type
rpc UploadChunks (stream FileChunk) returns (UploadResult);
// Bidirectional streaming
rpc Chat (stream ChatMessage) returns (stream ChatMessage);
}
Achieving bidirectional streaming with REST/HTTP/1.1 requires workarounds like WebSockets or Server-Sent Events — separate protocols with their own complexity. In gRPC, it's a first-class feature of the framework.
Interviewers frequently ask: "When would you choose gRPC over REST?" The structured answer: choose gRPC for internal service-to-service communication where you control both client and server, need high throughput (millions of RPC/sec), require streaming, or want compile-time schema enforcement. Choose REST for public APIs (browser clients, third-party integrations), when human readability matters for debugging, or when clients can't use HTTP/2. A nuanced addition: gRPC-Web exists for browser clients but requires a proxy layer (like Envoy) and doesn't support all streaming modes — it's not a drop-in replacement for REST in browser contexts.