🔌 gRPC 101

Three ideas you already know, bolted together: Protobuf for the bytes, HTTP/2 for the pipe, and generated stubs that make a network call look like a method call.

What gRPC Actually Is

gRPC is not one new thing — it's an assembly of parts that each have their own guide on this site:

The one-sentence definition: gRPC is Protobuf messages exchanged as HTTP/2 streams, with codegen so you call remote methods as if they were local functions. Everything else is detail.

The Layer Cake

Reading top to bottom, this is what a gRPC call is made of:

flowchart TB A["Your code
stub.GetUser(req)"] --> B["Generated stub
marshals Protobuf"] B --> C["gRPC runtime
frames message, sets :path"] C --> D["HTTP/2
one stream per call, multiplexed"] D --> E["TLS (usually)"] E --> F["TCP"]

Each layer only knows about the one below it. Your business code sees a method; the wire sees TCP bytes. That separation is exactly why the same .proto generates working clients in Go, Python, Java, and Rust with no hand-written wire code.

The Contract: a .proto File

Everything starts from an interface definition. You declare messages (the data) and a service (the callable methods):

syntax = "proto3";

package users.v1;

message GetUserRequest {
  string user_id = 1;
}

message User {
  string user_id = 1;
  string display_name = 2;
  string email = 3;
}

service UserService {
  // A plain request/response method (unary).
  rpc GetUser(GetUserRequest) returns (User);
}
The contract is the source of truth. Client and server never share code — they share this file. Regenerate on both sides and the wire stays compatible. See Schema Evolution for how to change it safely.

Codegen: Stubs and Servicers

Run the compiler and you get two generated artifacts per language:

# Generate Python code from the contract
# python -m grpc_tools.protoc -I. \
#     --python_out=. --grpc_python_out=. users/v1/user.proto

# -> user_pb2.py       (the message classes: GetUserRequest, User)
# -> user_pb2_grpc.py  (UserServiceStub + UserServiceServicer)

Channels vs Stubs

Channel = the connection. Stub = the typed API over it. One channel is expensive to create and safe to share; stubs are cheap wrappers you make freely on top of it.

The channel owns the HTTP/2 connection(s), name resolution, load-balancing, and connection state. Create it once and reuse it:

import grpc
from users.v1 import user_pb2, user_pb2_grpc

# One channel, long-lived, shared across the process.
channel = grpc.insecure_channel("users.internal:50051")
stub = user_pb2_grpc.UserServiceStub(channel)   # cheap wrapper

resp = stub.GetUser(user_pb2.GetUserRequest(user_id="u_123"))
print(resp.display_name)
Anti-pattern: creating a fresh channel per request. Channels do connection setup, TLS handshakes, and subchannel management — throwing that away every call kills throughput and leaks sockets. Make the channel once at startup.

The Server Side

Subclass the generated servicer, implement each method, and register it:

import grpc
from concurrent import futures
from users.v1 import user_pb2, user_pb2_grpc

class UserService(user_pb2_grpc.UserServiceServicer):
    def GetUser(self, request, context):
        user = db.load(request.user_id)          # your logic
        return user_pb2.User(
            user_id=user.id,
            display_name=user.name,
            email=user.email,
        )

server = grpc.server(futures.ThreadPoolExecutor(max_workers=16))
user_pb2_grpc.add_UserServiceServicer_to_server(UserService(), server)
server.add_insecure_port("[::]:50051")
server.start()
server.wait_for_termination()

Every handler receives a context — the door to deadlines, cancellation, metadata, and status codes. It shows up in every other guide in this collection.

How One Call Maps to HTTP/2

A gRPC call is a perfectly ordinary HTTP/2 request/response — with conventions:

sequenceDiagram participant C as Client stub participant S as Server C->>S: HEADERS :method=POST, :path=/users.v1.UserService/GetUser Note over C,S: content-type: application/grpc+proto C->>S: DATA length-prefixed Protobuf message S-->>C: HEADERS :status=200 (response headers) S-->>C: DATA length-prefixed Protobuf message S-->>C: HEADERS grpc-status: 0 (trailers)
Why HTTP/2 specifically: gRPC needs many concurrent calls on one connection, header compression, and full-duplex streaming — exactly the three things HTTP/2 added over HTTP/1.1. Streaming RPCs are just an HTTP/2 stream that stays open with more DATA frames. See Head-of-Line Blocking for the one place this still bites.

Key Takeaways

PieceRoleGuide
ProtobufSerializes messages to compact binaryProtobuf Basics
HTTP/2Multiplexed, full-duplex transportHTTP/2
.protoThe shared contract → codegenThis page
ChannelLong-lived connection, made onceThis page
Stub / ServicerGenerated client / server surfacesThis page
Real-world: gRPC shines for internal service-to-service traffic — typed contracts, low overhead, and streaming out of the box. For public, browser-facing APIs the tradeoffs shift; weigh them in gRPC vs REST.