gRPC Reference: Protobuf, Go/Python Servers, Streaming & gRPC-Gateway
gRPC uses HTTP/2 and Protocol Buffers for strongly-typed, high-performance RPC. The key advantages: binary protocol (smaller payloads), multiplexed streams, bidirectional streaming, and generated type-safe clients in any language. The trade-off: no browser support without gRPC-Web proxy, and JSON debugging isn’t possible without tooling.
1. Protobuf Schema & Code Generation
Write a .proto file, generate Go/Python/Node stubs, and understand field types
// users.proto — service definition:
syntax = "proto3";
package users.v1;
option go_package = "github.com/myorg/api/users/v1";
// Request/response messages:
message GetUserRequest {
string user_id = 1; // field number 1 (binary wire format tag)
}
message User {
string id = 1;
string name = 2;
string email = 3;
int32 age = 4;
repeated string tags = 5; // repeated = array
optional string phone = 6; // optional = may be absent
map<string, string> metadata = 7; // map field
UserStatus status = 8;
}
enum UserStatus {
USER_STATUS_UNSPECIFIED = 0; // always include 0 as default
USER_STATUS_ACTIVE = 1;
USER_STATUS_INACTIVE = 2;
}
// Service definition:
service UserService {
rpc GetUser(GetUserRequest) returns (User);
rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
rpc CreateUser(CreateUserRequest) returns (User);
rpc WatchUsers(WatchUsersRequest) returns (stream UserEvent); // server streaming
rpc Chat(stream ChatMessage) returns (stream ChatMessage); // bidirectional
}
# Generate Go stubs:
protoc --go_out=. --go-grpc_out=. users.proto
# Generate Python stubs:
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. users.proto
# Generate TypeScript (with ts-proto):
protoc --plugin=./node_modules/.bin/protoc-gen-ts_proto \
--ts_proto_out=. users.proto
2. Go Server & Client
Implement a gRPC server, interceptors, and typed client calls
package main
import (
"context"
"log"
"net"
usersv1 "github.com/myorg/api/users/v1"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// Server implementation (implements the generated interface):
type UserServer struct {
usersv1.UnimplementedUserServiceServer // embed for forward-compat
db *DB
}
func (s *UserServer) GetUser(ctx context.Context, req *usersv1.GetUserRequest) (*usersv1.User, error) {
user, err := s.db.GetUser(ctx, req.UserId)
if err != nil {
return nil, status.Errorf(codes.NotFound, "user %s not found", req.UserId)
}
return &usersv1.User{Id: user.ID, Name: user.Name, Email: user.Email}, nil
}
// gRPC error codes (use these, not HTTP-like codes):
// codes.NotFound, codes.InvalidArgument, codes.Unauthenticated
// codes.PermissionDenied, codes.Internal, codes.Unavailable
// codes.AlreadyExists, codes.DeadlineExceeded
// Server with interceptors (middleware):
func main() {
lis, _ := net.Listen("tcp", ":50051")
srv := grpc.NewServer(
grpc.UnaryInterceptor(loggingInterceptor),
grpc.StreamInterceptor(streamInterceptor),
)
usersv1.RegisterUserServiceServer(srv, &UserServer{})
srv.Serve(lis)
}
func loggingInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
log.Printf("gRPC call: %s", info.FullMethod)
resp, err := handler(ctx, req)
if err != nil {
log.Printf("error: %v", err)
}
return resp, err
}
// Client:
conn, err := grpc.NewClient("localhost:50051",
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
client := usersv1.NewUserServiceClient(conn)
defer conn.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
user, err := client.GetUser(ctx, &usersv1.GetUserRequest{UserId: "123"})
3. Python Server & Client
grpcio server, async client with grpcio-status error handling
# pip install grpcio grpcio-tools grpcio-status
# server.py:
import grpc
from concurrent import futures
import users_pb2
import users_pb2_grpc
class UserServicer(users_pb2_grpc.UserServiceServicer):
def GetUser(self, request, context):
user = db.get_user(request.user_id)
if not user:
context.set_code(grpc.StatusCode.NOT_FOUND)
context.set_details(f"User {request.user_id} not found")
return users_pb2.User()
return users_pb2.User(id=user.id, name=user.name, email=user.email)
# Server streaming:
def WatchUsers(self, request, context):
for event in watch_user_events():
if context.is_active(): # check if client disconnected
yield users_pb2.UserEvent(user_id=event.user_id, type=event.type)
else:
break
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
users_pb2_grpc.add_UserServiceServicer_to_server(UserServicer(), server)
server.add_insecure_port("[::]:50051")
server.start()
server.wait_for_termination()
# Async client (grpcio supports asyncio):
import grpc.aio
async def get_user(user_id: str):
async with grpc.aio.insecure_channel("localhost:50051") as channel:
stub = users_pb2_grpc.UserServiceStub(channel)
try:
user = await stub.GetUser(
users_pb2.GetUserRequest(user_id=user_id),
timeout=5.0
)
return user
except grpc.aio.AioRpcError as e:
if e.code() == grpc.StatusCode.NOT_FOUND:
return None
raise
4. Streaming (Server, Client, Bidirectional)
All three streaming patterns with backpressure and cancellation
// Go — server streaming (server sends many, client reads):
func (s *UserServer) ListUsers(req *usersv1.ListUsersRequest, stream usersv1.UserService_ListUsersServer) error {
users, _ := s.db.ListAll(stream.Context())
for _, user := range users {
if err := stream.Send(&usersv1.User{Id: user.ID, Name: user.Name}); err != nil {
return err // client disconnected
}
}
return nil
}
// Go client consuming server stream:
stream, err := client.ListUsers(ctx, &usersv1.ListUsersRequest{})
for {
user, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
log.Printf("stream error: %v", err)
break
}
fmt.Println(user.Name)
}
// Go — client streaming (client sends many, server reads):
stream, _ := client.BulkCreateUsers(ctx)
for _, u := range usersToCreate {
stream.Send(&usersv1.CreateUserRequest{Name: u.Name, Email: u.Email})
}
response, _ := stream.CloseAndRecv() // close send, get single response
// Go — bidirectional streaming:
stream, _ := client.Chat(ctx)
go func() {
for {
msg, err := stream.Recv()
if err != nil { break }
fmt.Println(msg.Text)
}
}()
for _, msg := range messages {
stream.Send(&usersv1.ChatMessage{Text: msg})
}
stream.CloseSend()
// Cancellation (client-side timeout or cancel):
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// Or: ctx, cancel := context.WithCancel(context.Background())
// stream.Context().Done() fires when client cancels
5. TLS, Metadata & gRPC-Gateway
Mutual TLS, request metadata (headers), and HTTP/JSON transcoding with gRPC-Gateway
// TLS (production — always use in prod):
creds, _ := credentials.NewServerTLSFromFile("cert.pem", "key.pem")
srv := grpc.NewServer(grpc.Creds(creds))
// Mutual TLS (client cert required):
tlsConfig := &tls.Config{
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: certPool,
}
creds := credentials.NewTLS(tlsConfig)
// Metadata (like HTTP headers — pass auth tokens, request IDs):
// Client sending metadata:
md := metadata.New(map[string]string{
"authorization": "Bearer " + token,
"x-request-id": requestID,
})
ctx = metadata.NewOutgoingContext(ctx, md)
// Server reading metadata:
md, ok := metadata.FromIncomingContext(ctx)
authHeader := md.Get("authorization") // returns []string
// Auth interceptor:
func authInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
md, _ := metadata.FromIncomingContext(ctx)
token := strings.TrimPrefix(md.Get("authorization")[0], "Bearer ")
if !validateToken(token) {
return nil, status.Error(codes.Unauthenticated, "invalid token")
}
return handler(ctx, req)
}
// gRPC-Gateway (expose REST endpoint on top of gRPC):
// In .proto, add HTTP annotations:
// rpc GetUser(GetUserRequest) returns (User) {
// option (google.api.http) = { get: "/v1/users/{user_id}" };
// }
// Then: protoc --grpc-gateway_out=. users.proto
// Runs a reverse proxy: HTTP/JSON <--> gRPC
// Debug tools:
// grpcurl -plaintext localhost:50051 list
// grpcurl -plaintext -d '{"user_id":"123"}' localhost:50051 users.v1.UserService/GetUser
// grpcui -plaintext localhost:50051 # browser UI for gRPC
Track gRPC and API tooling releases at ReleaseRun. Related: OpenAPI Reference | Go Modules Reference | Kubernetes Gateway API Reference
🔍 Free tool: npm Package Health Checker — check @grpc/grpc-js and related gRPC packages for known CVEs and active maintenance.
Founded
2023 in London, UK
Contact
hello@releaserun.com