DevToolBoxZA DARMO
Blog

gRPC Guide 2026: Protocol Buffers, Streaming, Node.js, Go, Python & Best Practices

22 min readby DevToolBox
TL;DR

gRPC is a high-performance RPC framework using Protocol Buffers for serialization and HTTP/2 for transport. It supports unary, server streaming, client streaming, and bidirectional streaming. Define services in .proto files, generate type-safe client/server code, and get built-in features like TLS, deadlines, and interceptors. Use gRPC for internal microservices; use REST or gRPC-Web for browser-facing APIs.

1. What is gRPC?

gRPC (gRPC Remote Procedure Call) is an open-source high-performance RPC framework originally developed by Google. It uses Protocol Buffers (protobuf) as its interface definition language and serialization format, and runs on HTTP/2 for transport. This combination gives gRPC binary efficiency, multiplexed connections, header compression, and native streaming support.

Unlike REST where you design around resources and HTTP verbs, gRPC lets you define services with methods that clients call as if they were local function calls. The protobuf compiler generates strongly typed client and server stubs in over a dozen languages, eliminating manual serialization code.

2. Protocol Buffers Primer

Protocol Buffers (protobuf) is a language-neutral, platform-neutral mechanism for serializing structured data. You define your data shape once in a .proto file, then generate code for any target language.

Basic Message Definition

syntax = "proto3";

package user;

message User {
  string id = 1;
  string name = 2;
  string email = 3;
  int32 age = 4;
  repeated string roles = 5;    // list of strings
  Address address = 6;          // nested message
}

message Address {
  string street = 1;
  string city = 2;
  string country = 3;
  string zip_code = 4;
}

Each field has a type, a name, and a unique field number. Field numbers are used in the binary encoding and must not change once your message type is in use. The repeated keyword indicates a list. Proto3 makes all fields optional by default.

Protobuf vs JSON Size Comparison

FormatPayload SizeEncode TimeDecode Time
JSON~250 bytes~1.2 ms~1.5 ms
Protobuf~48 bytes~0.15 ms~0.12 ms

3. gRPC vs REST: When to Use Which

CriteriagRPCREST
SerializationProtobuf (binary)JSON (text)
TransportHTTP/2HTTP/1.1 or HTTP/2
StreamingBuilt-in (4 patterns)SSE or WebSocket
Code generationNative (protoc)Optional (OpenAPI)
Browser supportVia gRPC-Web proxyNative
Best forMicroservices, streaming, polyglotPublic APIs, CRUD, simple integrations

Rule of thumb: Use gRPC for internal service-to-service communication where performance and type safety matter. Use REST for public APIs consumed by third-party developers or browser clients without a proxy layer.

4. Defining Services with .proto Files

A gRPC service definition declares the RPC methods available to clients. Each method specifies a request and response message type, and optionally uses the stream keyword.

syntax = "proto3";
package order;

service OrderService {
  rpc CreateOrder (CreateOrderRequest) returns (Order);           // unary
  rpc GetOrder (GetOrderRequest) returns (Order);                 // unary
  rpc ListOrders (ListOrdersRequest) returns (stream Order);     // server stream
  rpc UploadLineItems (stream LineItem) returns (UploadSummary); // client stream
  rpc OrderChat (stream ChatMessage) returns (stream ChatMessage); // bidi
}

message CreateOrderRequest {
  string customer_id = 1;
  repeated LineItem items = 2;
}

message LineItem {
  string product_id = 1;
  int32 quantity = 2;
  double unit_price = 3;
}

message Order {
  string id = 1;
  string customer_id = 2;
  repeated LineItem items = 3;
  double total = 4;
  OrderStatus status = 5;
}

enum OrderStatus {
  ORDER_STATUS_UNSPECIFIED = 0;
  ORDER_STATUS_PENDING = 1;
  ORDER_STATUS_CONFIRMED = 2;
  ORDER_STATUS_SHIPPED = 3;
}

Compile the proto file to generate language-specific code:

# Generate Go code
protoc --go_out=. --go-grpc_out=. proto/order.proto

# Generate Node.js/TypeScript code
protoc --ts_out=./src/gen proto/order.proto

# Generate Python code
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. proto/order.proto

5. Streaming Patterns Explained

PatternProto SyntaxUse Cases
Unaryrpc Get(Req) returns (Resp)CRUD, auth, lookups
Server streamingrpc List(Req) returns (stream Resp)Feeds, log tailing, large result sets
Client streamingrpc Upload(stream Req) returns (Resp)File uploads, batch ingestion, telemetry
Bidirectionalrpc Chat(stream Msg) returns (stream Msg)Chat, collaborative editing, game state

The stream keyword in the proto definition controls the pattern. Both sides read and write independently in bidirectional mode, and the two streams operate over a single HTTP/2 connection.

6. gRPC in Node.js / TypeScript

The @grpc/grpc-js package is the official pure-JavaScript gRPC client and server. Pair it with @grpc/proto-loader for dynamic loading or ts-proto for generated TypeScript types.

Server Implementation

import * as grpc from "@grpc/grpc-js";
import * as protoLoader from "@grpc/proto-loader";

const PROTO_PATH = "./proto/order.proto";

const packageDef = protoLoader.loadSync(PROTO_PATH, {
  keepCase: true,
  longs: String,
  enums: String,
  defaults: true,
  oneofs: true,
});

const proto = grpc.loadPackageDefinition(packageDef) as any;

function createOrder(
  call: grpc.ServerUnaryCall<any, any>,
  callback: grpc.sendUnaryData<any>
) {
  const request = call.request;
  const order = {
    id: "ord-" + Date.now(),
    customer_id: request.customer_id,
    items: request.items,
    total: request.items.reduce(
      (sum: number, item: any) => sum + item.quantity * item.unit_price, 0
    ),
    status: "ORDER_STATUS_PENDING",
  };
  callback(null, order);
}

function listOrders(
  call: grpc.ServerWritableStream<any, any>
) {
  // Server streaming: push orders one by one
  const orders = getOrdersFromDB(call.request.customer_id);
  for (const order of orders) {
    call.write(order);
  }
  call.end();
}

const server = new grpc.Server();
server.addService(proto.order.OrderService.service, {
  createOrder,
  listOrders,
});

server.bindAsync(
  "0.0.0.0:50051",
  grpc.ServerCredentials.createInsecure(),
  (err, port) => {
    if (err) throw err;
    console.log("gRPC server running on port " + port);
  }
);

Client Implementation

const client = new proto.order.OrderService(
  "localhost:50051",
  grpc.credentials.createInsecure()
);

// Unary call
client.createOrder(
  { customer_id: "cust-1", items: [{ product_id: "p-1", quantity: 2, unit_price: 29.99 }] },
  (err: any, response: any) => {
    if (err) return console.error("Error:", err.message);
    console.log("Order created:", response.id);
  }
);

// Server streaming
const stream = client.listOrders({ customer_id: "cust-1" });
stream.on("data", (order: any) => console.log("Order:", order.id));
stream.on("end", () => console.log("All orders received"));
stream.on("error", (err: any) => console.error("Error:", err.message));

7. gRPC in Go

Go has first-class gRPC support through google.golang.org/grpc. The generated code provides interfaces you implement for the server and ready-to-use clients with full type safety.

Server Implementation

package main

import (
    "context"
    "fmt"
    "log"
    "net"
    "time"

    "google.golang.org/grpc"
    pb "myapp/gen/order"
)

type orderServer struct {
    pb.UnimplementedOrderServiceServer
}

func (s *orderServer) CreateOrder(
    ctx context.Context,
    req *pb.CreateOrderRequest,
) (*pb.Order, error) {
    order := &pb.Order{
        Id:         fmt.Sprintf("ord-%d", time.Now().UnixMilli()),
        CustomerId: req.CustomerId,
        Items:      req.Items,
        Status:     pb.OrderStatus_ORDER_STATUS_PENDING,
    }
    return order, nil
}

func (s *orderServer) ListOrders(
    req *pb.ListOrdersRequest,
    stream pb.OrderService_ListOrdersServer,
) error {
    orders := getOrdersFromDB(req.CustomerId)
    for _, order := range orders {
        if err := stream.Send(order); err != nil {
            return err
        }
    }
    return nil
}

func main() {
    lis, err := net.Listen("tcp", ":50051")
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }

    srv := grpc.NewServer()
    pb.RegisterOrderServiceServer(srv, &orderServer{})

    log.Println("gRPC server listening on :50051")
    if err := srv.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

Client Implementation

conn, err := grpc.Dial(
    "localhost:50051",
    grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
    log.Fatalf("failed to connect: %v", err)
}
defer conn.Close()

client := pb.NewOrderServiceClient(conn)

// Unary call with deadline
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

order, err := client.CreateOrder(ctx, &pb.CreateOrderRequest{
    CustomerId: "cust-1",
    Items: []*pb.LineItem{
        {ProductId: "p-1", Quantity: 2, UnitPrice: 29.99},
    },
})
fmt.Println("Created order:", order.Id)

// Server streaming — read until EOF
stream, _ := client.ListOrders(ctx, &pb.ListOrdersRequest{CustomerId: "cust-1"})
for {
    o, err := stream.Recv()
    if err == io.EOF { break }
    if err != nil { log.Fatal(err) }
    fmt.Println("Order:", o.Id)
}

8. gRPC in Python

Python uses grpcio and grpcio-tools packages. The generated code provides typed stubs for both sync and async usage.

Server Implementation

import grpc
from concurrent import futures
import time
import order_pb2
import order_pb2_grpc

class OrderServicer(order_pb2_grpc.OrderServiceServicer):
    def CreateOrder(self, request, context):
        total = sum(
            item.quantity * item.unit_price
            for item in request.items
        )
        return order_pb2.Order(
            id=f"ord-{int(time.time() * 1000)}",
            customer_id=request.customer_id,
            items=request.items,
            total=total,
            status=order_pb2.ORDER_STATUS_PENDING,
        )

    def ListOrders(self, request, context):
        orders = get_orders_from_db(request.customer_id)
        for order in orders:
            yield order  # server streaming

def serve():
    server = grpc.server(
        futures.ThreadPoolExecutor(max_workers=10)
    )
    order_pb2_grpc.add_OrderServiceServicer_to_server(
        OrderServicer(), server
    )
    server.add_insecure_port("[::]:50051")
    server.start()
    print("gRPC server running on port 50051")
    server.wait_for_termination()

if __name__ == "__main__":
    serve()

Client with Deadline

import grpc
import order_pb2
import order_pb2_grpc

channel = grpc.insecure_channel("localhost:50051")
stub = order_pb2_grpc.OrderServiceStub(channel)

# Unary call with 5-second deadline
try:
    response = stub.CreateOrder(
        order_pb2.CreateOrderRequest(
            customer_id="cust-1",
            items=[
                order_pb2.LineItem(
                    product_id="p-1",
                    quantity=2,
                    unit_price=29.99,
                )
            ],
        ),
        timeout=5.0,
    )
    print(f"Created order: {response.id}")
except grpc.RpcError as e:
    print(f"RPC failed: {e.code()} - {e.details()}")

# Server streaming
for order in stub.ListOrders(
    order_pb2.ListOrdersRequest(customer_id="cust-1")
):
    print(f"Order: {order.id}")

9. Authentication and Security

gRPC provides channel-level encryption with TLS and call-level authentication via metadata. Production services should always use TLS.

TLS Configuration (Go Server)

import "google.golang.org/grpc/credentials"

creds, err := credentials.NewServerTLSFromFile(
    "server.crt",
    "server.key",
)
if err != nil {
    log.Fatalf("failed to load TLS: %v", err)
}

srv := grpc.NewServer(grpc.Creds(creds))

Token-Based Auth with Interceptors (Go)

import (
    "context"
    "google.golang.org/grpc"
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/metadata"
    "google.golang.org/grpc/status"
)

func authInterceptor(
    ctx context.Context,
    req interface{},
    info *grpc.UnaryServerInfo,
    handler grpc.UnaryHandler,
) (interface{}, error) {
    md, ok := metadata.FromIncomingContext(ctx)
    if !ok {
        return nil, status.Error(
            codes.Unauthenticated, "missing metadata",
        )
    }

    tokens := md.Get("authorization")
    if len(tokens) == 0 {
        return nil, status.Error(
            codes.Unauthenticated, "missing token",
        )
    }

    if err := validateToken(tokens[0]); err != nil {
        return nil, status.Error(
            codes.Unauthenticated, "invalid token",
        )
    }

    return handler(ctx, req)
}

// Register the interceptor
srv := grpc.NewServer(
    grpc.UnaryInterceptor(authInterceptor),
)

Sending Auth Token from Client (Node.js)

const metadata = new grpc.Metadata();
metadata.set("authorization", "Bearer " + token);

client.createOrder(
  { customer_id: "cust-1", items: [] },
  metadata,
  (err, response) => {
    if (err) console.error(err);
    else console.log("Order:", response.id);
  }
);

10. Error Handling and Status Codes

gRPC defines a standard set of status codes. Always return meaningful codes and messages so clients can handle errors appropriately.

CodeNameUse Case
0OKSuccess
1CANCELLEDClient cancelled the request
3INVALID_ARGUMENTBad request data (validation)
4DEADLINE_EXCEEDEDRequest took too long
5NOT_FOUNDResource does not exist
7PERMISSION_DENIEDAuthenticated but not authorized
13INTERNALServer-side bug
14UNAVAILABLEService temporarily down (retry)
16UNAUTHENTICATEDMissing or invalid credentials

Rich Error Details (Go)

import (
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/status"
    "google.golang.org/genproto/googleapis/rpc/errdetails"
)

func (s *orderServer) GetOrder(
    ctx context.Context,
    req *pb.GetOrderRequest,
) (*pb.Order, error) {
    order, err := s.db.FindOrder(req.Id)
    if err != nil {
        st := status.New(codes.NotFound, "order not found")
        detail := &errdetails.ErrorInfo{
            Reason: "ORDER_NOT_FOUND",
            Domain: "order.myapp.com",
            Metadata: map[string]string{
                "order_id": req.Id,
            },
        }
        st, _ = st.WithDetails(detail)
        return nil, st.Err()
    }
    return order, nil
}

Error Handling on the Client (Python)

try:
    order = stub.GetOrder(
        order_pb2.GetOrderRequest(id="ord-missing"),
        timeout=5.0,
    )
except grpc.RpcError as e:
    if e.code() == grpc.StatusCode.NOT_FOUND:
        print("Order not found:", e.details())
    elif e.code() == grpc.StatusCode.DEADLINE_EXCEEDED:
        print("Request timed out")
    elif e.code() == grpc.StatusCode.UNAVAILABLE:
        print("Service down, retrying...")
    else:
        print(f"RPC error: {e.code()} - {e.details()}")

11. gRPC-Web for Browser Clients

Browsers cannot make native gRPC calls due to missing HTTP/2 trailer support. gRPC-Web bridges this gap by translating browser-compatible requests into native gRPC through a proxy.

Architecture

Browser (gRPC-Web client)
    |
    | HTTP/1.1 or HTTP/2 (no trailers)
    v
Envoy Proxy (or grpc-web middleware)
    |
    | Native gRPC over HTTP/2
    v
gRPC Backend Server

Envoy Proxy Configuration (Key Parts)

# envoy.yaml — essential gRPC-Web filters
static_resources:
  listeners:
    - name: listener_0
      address:
        socket_address: { address: 0.0.0.0, port_value: 8080 }
      filter_chains:
        - filters:
            - name: envoy.filters.network.http_connection_manager
              typed_config:
                http_filters:
                  - name: envoy.filters.http.grpc_web  # key filter
                  - name: envoy.filters.http.cors
                  - name: envoy.filters.http.router
  clusters:
    - name: grpc_service
      type: logical_dns
      http2_protocol_options: {}  # must enable HTTP/2
      load_assignment:
        endpoints:
          - lb_endpoints:
              - endpoint:
                  address:
                    socket_address:
                      address: backend
                      port_value: 50051

Browser Client (TypeScript)

import { OrderServiceClient } from "./gen/order_grpc_web_pb";
import { CreateOrderRequest, LineItem } from "./gen/order_pb";

const client = new OrderServiceClient(
  "https://api.example.com:8080"
);

const item = new LineItem();
item.setProductId("p-1");
item.setQuantity(2);
item.setUnitPrice(29.99);

const request = new CreateOrderRequest();
request.setCustomerId("cust-1");
request.setItemsList([item]);

client.createOrder(request, {}, (err, response) => {
  if (err) {
    console.error("gRPC-Web error:", err.message);
    return;
  }
  console.log("Order ID:", response.getId());
});

12. Load Balancing and Service Mesh

gRPC uses long-lived HTTP/2 connections, which means a traditional L4 (TCP) load balancer will send all requests from one connection to the same backend. You need L7 (application-layer) balancing or client-side balancing.

Load Balancing Strategies

StrategyHow It WorksProsCons
L7 Proxy (Envoy)Proxy understands HTTP/2 framesPer-request balancing, no client changesExtra hop latency
Client-side (round-robin)Client discovers backends and distributesNo proxy needed, lowest latencyClient complexity, stale endpoints
Service Mesh (Istio)Sidecar proxy per podTransparent, advanced featuresOperational overhead, resource usage

Kubernetes Headless Service

# Headless Service for client-side load balancing
apiVersion: v1
kind: Service
metadata:
  name: order-service
spec:
  clusterIP: None   # returns all pod IPs for DNS
  selector:
    app: order-service
  ports:
    - port: 50051
      targetPort: 50051
---
# Deployment with gRPC health probe
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 3
  template:
    spec:
      containers:
        - name: order-service
          image: myapp/order-service:latest
          ports:
            - containerPort: 50051
          readinessProbe:
            grpc:
              port: 50051

13. Best Practices for Production

Always Set Deadlines

Never make a gRPC call without a deadline. Without deadlines, a slow or stuck server can hold client resources indefinitely, cascading failures across services.

// Go: always set a deadline
ctx, cancel := context.WithTimeout(
    context.Background(),
    5*time.Second,
)
defer cancel()

// Node.js: set deadline in options
// const deadline = new Date();
// deadline.setSeconds(deadline.getSeconds() + 5);
// client.createOrder(request, { deadline }, callback);

// Python: timeout parameter
// stub.CreateOrder(request, timeout=5.0)

Use Interceptors for Cross-Cutting Concerns

// Go: chain logging + auth + rate limit interceptors
srv := grpc.NewServer(
    grpc.ChainUnaryInterceptor(
        loggingInterceptor,   // logs method, duration, error
        authInterceptor,      // validates JWT token
        rateLimitInterceptor, // token-bucket per client
    ),
)

Health Checks and Reflection

import (
    "google.golang.org/grpc/health"
    "google.golang.org/grpc/health/grpc_health_v1"
    "google.golang.org/grpc/reflection"
)

// Enable health checking
healthSrv := health.NewServer()
grpc_health_v1.RegisterHealthServer(srv, healthSrv)
healthSrv.SetServingStatus(
    "order.OrderService",
    grpc_health_v1.HealthCheckResponse_SERVING,
)

// Enable reflection (for grpcurl, grpcui)
reflection.Register(srv)

// Now you can introspect with grpcurl:
// grpcurl -plaintext localhost:50051 list
// grpcurl -plaintext localhost:50051 order.OrderService/CreateOrder

Proto File Best Practices

  • Never reuse field numbers — deleted fields should be reserved
  • Use packages — namespace your protos to avoid conflicts
  • Prefix enum values — e.g., ORDER_STATUS_PENDING not just PENDING
  • Add a zero value for enums — first value should be UNSPECIFIED = 0
  • Use wrapper types for optional scalarsgoogle.protobuf.Int32Value
  • Version your APIs — use package myapp.order.v1
  • Lint your protos — use buf lint to enforce style rules

Retry and Backoff

// Go: retry policy via service config JSON
serviceConfig := `{"methodConfig":[{"name":[{"service":"order.OrderService"}],
  "retryPolicy":{"maxAttempts":3,"initialBackoff":"0.1s",
  "maxBackoff":"1s","backoffMultiplier":2.0,
  "retryableStatusCodes":["UNAVAILABLE","DEADLINE_EXCEEDED"]}}]}`

conn, _ := grpc.Dial("localhost:50051",
    grpc.WithDefaultServiceConfig(serviceConfig),
    grpc.WithTransportCredentials(insecure.NewCredentials()),
)

Graceful Shutdown

// Go: graceful shutdown
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit

log.Println("Shutting down gRPC server...")
srv.GracefulStop()  // waits for in-flight RPCs
log.Println("Server stopped")
Key Takeaways
  • gRPC uses Protocol Buffers for binary serialization — 5-10x faster and smaller than JSON
  • HTTP/2 multiplexing enables streaming and eliminates head-of-line blocking
  • Define services once in .proto files, generate clients in any supported language
  • Four communication patterns: unary, server streaming, client streaming, bidirectional
  • Use gRPC-Web or a gateway proxy to expose gRPC services to browser clients
  • Always set deadlines, use interceptors for cross-cutting concerns, and enable TLS in production
  • gRPC integrates natively with Kubernetes, Istio, and Envoy for load balancing
  • Use health checks, reflection, and structured status codes for production readiness
𝕏 Twitterin LinkedIn
Czy to było pomocne?

Bądź na bieżąco

Otrzymuj cotygodniowe porady i nowe narzędzia.

Bez spamu. Zrezygnuj kiedy chcesz.

Try These Related Tools

{ }JSON FormatterB→Base64 Encoder#Hash Generator

Related Articles

Microservices Guide: Architecture, Communication Patterns, and Best Practices

Master microservices architecture. Covers service communication (REST/gRPC/Kafka), API Gateway, service discovery, distributed tracing, CQRS, Saga pattern, Docker, Kubernetes, and observability.

API Design Guide: REST Best Practices, OpenAPI, Auth, Pagination, and Caching

Master API design. Covers REST principles, versioning strategies, JWT/OAuth 2.0 authentication, OpenAPI/Swagger specification, rate limiting, RFC 7807 error handling, pagination patterns, ETags caching, and REST vs GraphQL vs gRPC vs tRPC comparison.

NestJS Complete Guide: Modules, Controllers, Services, DI, TypeORM, JWT Auth, and Testing

Master NestJS from scratch. Covers modules, controllers, services, providers, dependency injection, TypeORM/Prisma database integration, JWT authentication, Guards, Pipes, Interceptors, Exception Filters, and unit/e2e testing with Jest.