General

gRPC Explained: Why Microservices Chose Protocol Buffers Over REST

TL;DR REST models resources and HTTP verbs: GET /orders/123, DELETE /orders/123. RPC models operations and procedures: GetOrder(id: 123), CancelOrder(id: 123). REST is resource-oriented; gRPC is action-oriented. Neither is universally better — but for internal microservice communication

Read this article as text (accessible version)
gRPC · Protocol Buffers · Microservices 3,800 words · 4 interactive labs · April 2026

gRPC Explained:
Why Microservices
Chose Protocol Buffers
Over REST

Your microservices are talking to each other over REST+JSON. Every call serializes a fat JSON blob, sends it over HTTP/1.1, waits for a response, deserializes it. At 50 RPS across 12 services, this is fine. At 50,000 RPS — and your payment service is now a critical bottleneck — it's time to understand why Google rewrote their internal RPC system and open-sourced it as gRPC.

Read the Deep Dive ↓ Open the Lab 📡 Protocol Buffers (binary) HTTP/2 multiplexed streams 4 streaming patterns Auto-generated client stubs 5× faster than JSON Table of Contents
  1. What Is RPC — and Why It Matters
  2. Protocol Buffers: Typed, Binary, Fast
  3. HTTP/2: The Performance Foundation
  4. The gRPC Call Flow End-to-End
  5. 4 Streaming Patterns
  6. Why gRPC Doesn't Work in Browsers
  7. When to Use gRPC vs REST

01What Is RPC — and Why the Abstraction Changes Everything

Your order service needs to charge a customer. It has two options. Option A: make an HTTP POST to /payments/charge, serialize a JSON body, wait for a 200 OK response, parse the JSON back. Option B: call paymentService.charge(customerId, amount, currency) — as if it were a local function call — and get back a typed ChargeResult. The network still happens. The bytes still travel. But from your code's perspective, it feels exactly like calling a local function.

That's the promise of Remote Procedure Call (RPC). A local procedure call is a function call within a process — function A calls function B, gets a result, continues. RPC extends this model across the network: one machine invokes code on another machine, and the calling code doesn't need to manage serialization, deserialization, connection management, or retries explicitly. The RPC framework handles the plumbing; you write business logic.

RPC is not a new idea — CORBA, XML-RPC, SOAP, and Thrift all attempted variations of this pattern. What makes gRPC different is the combination of a modern serialization format (Protocol Buffers), a high-performance transport (HTTP/2), and exceptional multi-language code generation tooling. When Google open-sourced gRPC in 2016, it was built on the same internal RPC infrastructure they'd been running at planet scale for years under the name Stubby/Borg. The experience showed. gRPC was production-battle-tested before most teams even knew it existed.

Here's the thing most tutorials miss about RPC as a model: the abstraction is a double-edged sword. By hiding network calls behind a function-call interface, RPC can make distributed systems feel deceptively simple. A local function call never fails with a network timeout, never partially completes, never needs to handle idempotency. A remote call does all of these things. Understanding gRPC means understanding not just the happy path but the failure modes that the abstraction conceals — timeouts, partial failures, retry storms — and designing your services to handle them explicitly.

💡 RPC vs REST: The Mental Model Shift

REST models resources and HTTP verbs: GET /orders/123, DELETE /orders/123. RPC models operations and procedures: GetOrder(id: 123), CancelOrder(id: 123). REST is resource-oriented; gRPC is action-oriented. Neither is universally better — but for internal microservice communication where both ends control the API contract, gRPC's action-oriented model and type-safe generated code dramatically reduce integration bugs and boilerplate. The REST model's advantage is human-readability and browser-native compatibility, which matters enormously for public APIs.

LLM next token prediction probability distribution diagram showing vocabulary tokens with probability bars and sampling mechanism

02Protocol Buffers: Typed, Binary, and Shockingly Compact

Imagine you need to describe a payment request: a customer ID, an amount, and a currency code. In JSON, this is {"customerId":"usr_abc123","amount":4999,"currency":"USD"} — 50 characters, 50 bytes. In Protocol Buffers (Protobuf), the same data encodes to roughly 20 bytes: the field numbers and types are encoded as single bytes rather than repeated field name strings, integers are encoded in variable-length format (small numbers take fewer bytes), and there are no delimiters or quotation marks. The format is binary, not human-readable, but machines don't need to read it — they need to process it fast.

The starting point for any gRPC service is a .proto file. This file defines both the data structures (called messages) and the service methods in a language-agnostic schema language. You run protoc (the Protocol Buffer compiler) with the appropriate language plugin, and it generates type-safe data classes and gRPC client/server boilerplate in Go, Python, Java, C++, Kotlin, Swift, Dart — or all of them simultaneously. The server team uses Go; the client team uses Python? No problem. Both compile from the same .proto contract and get type-safe generated code that automatically stays in sync.

The type safety is the less-appreciated benefit. REST+JSON APIs often drift between what the server sends and what clients expect — a field gets renamed, a type changes from string to integer, and the only way you find out is at runtime when something breaks. With Protobuf, the schema is the contract. If the payment service changes an integer field to a string, the generated client code won't compile until the client updates its Protobuf dependency. Type mismatches become compile-time errors, not production incidents.

✅ Protobuf Field Numbers Are Sacred — Never Reuse Them

Protobuf identifies fields by their field number (1, 2, 3...) in the serialized binary, not by their name. If you remove a field and add a new field with the same number but a different type, old clients sending binary blobs will corrupt the new field. The rule: once a field number is used, it is retired forever if the field is removed. Mark removed fields with the reserved keyword: reserved 3, 7;. This prevents accidentally reusing those numbers and creating silent data corruption bugs. For evolving APIs, always add new fields rather than removing or renaming — Protobuf handles unknown fields gracefully (ignores them) but mistyped fields are catastrophic.

payment.proto — defining service + messages
syntax = "proto3";
package payment;
option go_package = "./gen/payment";

Message: request payload (what client sends)
message ChargeRequest {
 string customer_id = 1; field number 1 — never reuse
 int64 amount_cents = 2; field number 2 — always cents!
 string currency = 3; "USD", "EUR", etc.
 string idempotency_key = 4; added later — safe, new number
}

Message: response payload (what server returns)
message ChargeResponse {
 string transaction_id = 1;
 bool success = 2;
 string error_message = 3; empty on success
}

Service: defines RPC methods
service PaymentService {
 rpc Charge(ChargeRequest) returns (ChargeResponse);
 rpc StreamCharges(ChargeRequest) returns (stream ChargeResponse);
}

# Generate Go client + server code:
# protoc --go_out=. --go-grpc_out=. payment.proto
# Generates: payment.pb.go (messages) + payment_grpc.pb.go (service)

03HTTP/2: The Transport That Makes gRPC Possible

HTTP/1.1 — the protocol underlying most REST APIs — has a fundamental limitation: one request per TCP connection at a time. To get concurrency, you open multiple TCP connections. A browser typically opens 6 connections per domain. Each connection has its own handshake overhead. Each request must wait for the previous response. The headers repeat verbatim with every request (no compression). This is fine for web pages, which load occasionally, but it's a performance ceiling for high-frequency microservice communication.

HTTP/2 redesigns the transport layer. The key innovation: streams. A single TCP connection can carry multiple independent request-response streams simultaneously, interleaved at the byte level. Stream 1 carries a payment request. Stream 2 carries an inventory check. Stream 3 carries a user profile lookup. All three are in-flight simultaneously over one TCP connection, without waiting for each other. HTTP/2 also compresses headers using HPACK, dramatically reducing overhead for repeated requests. And it supports server push — the server can preemptively send data the client will likely need next.

gRPC is built on HTTP/2 precisely for these properties. Each gRPC call runs as a stream. Server streaming, client streaming, and bidirectional streaming all exploit HTTP/2's multiplexing natively. The practical result: a gRPC service handling 1,000 concurrent RPC calls might do so over a small number of TCP connections — dramatically reducing connection overhead compared to a REST service needing hundreds of concurrent HTTP/1.1 connections. This is why gRPC benchmarks consistently show 5-10× throughput improvement over REST+JSON for high-concurrency microservice workloads.

⚡ Why HTTP/2 Solves Head-of-Line Blocking (Mostly)

HTTP/1.1 suffers from head-of-line blocking: a slow response blocks all subsequent requests on that connection. HTTP/2 streams solve this at the application layer — slow Stream 3 doesn't block Stream 7. However, TCP itself can still have head-of-line blocking at the transport layer: if a TCP packet is lost, all streams on that connection stall until retransmission completes. HTTP/3 (QUIC) solves this by moving multiplexing to the UDP layer, eliminating transport-layer head-of-line blocking entirely. Some gRPC implementations are beginning to support HTTP/3, which will further improve performance in lossy network environments like mobile.


04The gRPC Call Flow: From Stub to Wire and Back

Let's trace a single gRPC call from the Order Service to the Payment Service. This end-to-end journey reveals why gRPC feels like a local function call from the developer's perspective while being highly optimized at the transport level.

Step 1: The Order Service calls paymentClient.Charge(ctx, chargeReq). This doesn't reach out to the network directly — it calls the client stub, the auto-generated code that knows how to serialize the ChargeRequest message into Protobuf binary format. The stub handles all the gRPC protocol mechanics. Step 2: The stub serializes the message to binary, wraps it in an HTTP/2 DATA frame with the appropriate headers (content-type: application/grpc, the method path, deadline metadata), and sends it over an existing HTTP/2 connection to the Payment Service's address.

Step 3: The Payment Service's gRPC server receives the HTTP/2 frames, assembles the message, deserializes the Protobuf binary back into a ChargeRequest struct, and calls the server's handler function with the request. Step 4: The handler executes business logic (calls the payment processor, updates the database) and returns a ChargeResponse struct. Step 5: The generated server code serializes the response to Protobuf, wraps it in HTTP/2 DATA frames with a trailers frame containing the gRPC status code, and sends it back. Step 6: The client stub receives the frames, deserializes the response, and returns the typed ChargeResponse to the calling code. From the Order Service's perspective, step 1 to step 6 looked like a function call.

💡 Always Set Deadlines on gRPC Calls

By default, gRPC calls have no timeout — a call will wait indefinitely if the server doesn't respond. In production, this causes cascading failures: the Order Service waits forever for the Payment Service, accumulating goroutines/threads until it runs out of resources and crashes. Always set a deadline: ctx, cancel := context.WithTimeout(ctx, 5*time.Second). Set deadlines at the outermost service entry point and propagate them through the call chain. gRPC will automatically return a DEADLINE_EXCEEDED error if the operation doesn't complete in time. This is non-negotiable for production gRPC systems.

grpc_client.go — using the generated stub
package main

import (
 "context"
 "log"
 "time"
 pb "myapp/gen/payment" ← auto-generated from payment.proto
 "google.golang.org/grpc"
 "google.golang.org/grpc/credentials/insecure"
)

func main() {
 Create connection (reused across all calls — not per-call!)
 conn, _ := grpc.Dial("payment-service:50051",
 grpc.WithTransportCredentials(insecure.NewCredentials()),
 )
 defer conn.Close()

 Create the client stub (generated code)
 client := pb.NewPaymentServiceClient(conn)

 Always set a deadline — never let calls hang indefinitely
 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
 defer cancel()

 Make the RPC call — looks like a local function call!
 resp, err := client.Charge(ctx, &pb.ChargeRequest{
 CustomerId: "usr_abc123",
 AmountCents: 4999,
 Currency: "USD",
 IdempotencyKey: "order_789_charge_1",
 })
 if err != nil {
 gRPC status codes: codes.Unavailable, codes.DeadlineExceeded, etc.
 log.Fatalf("charge failed: %v", err)
 }
 log.Printf("transaction: %s, success: %v", resp.TransactionId, resp.Success)
}

054 Streaming Patterns: gRPC's Secret Weapon

REST is fundamentally request-response: you send one request, you get one response. gRPC supports four distinct communication patterns, three of which REST cannot efficiently replicate. Understanding these patterns opens up architectural possibilities that REST simply can't provide cleanly.

Unary RPC is the familiar pattern: one request, one response. This covers most microservice interactions — charging a payment, looking up a user, creating an order. Server streaming: the client sends one request and the server responds with a stream of messages. Perfect for real-time use cases like streaming log entries, live stock price feeds, or progressively delivering search results as they're found. Client streaming: the client sends a stream of messages and the server responds with a single response. Perfect for large file uploads, batch operations (upload 10,000 metrics, get one "processed" response), or sensor data aggregation. Bidirectional streaming: both client and server send streams simultaneously. This enables real-time chat, collaborative editing, live sensor-to-dashboard, or any application that previously would have required WebSockets.

The counterintuitive insight about bidirectional streaming: it's not just a performance feature — it's an architectural one. Bidirectional streaming changes which side initiates communication. In pure REST, the server can never push to the client without the client polling or using a separate mechanism (Server-Sent Events, WebSockets). With gRPC bidirectional streaming, the server can push messages to the client at any time while the stream is open. This dramatically simplifies architectures that need server-initiated communication — no separate WebSocket server, no polling infrastructure, no message queue bridge.

✅ Use Streaming for Push, Not Just Bulk Transfer

Most engineers reach for streaming only when they have "a lot of data" to send. But streaming's more powerful use is enabling server-push semantics within a gRPC call. A bidirectional stream between a control plane and a data plane agent (like Envoy's xDS protocol) lets the control plane push configuration updates to thousands of agents without each agent polling. This is the architecture behind Istio, Envoy, and most modern service meshes. When you see a system that "needs real-time push," gRPC bidirectional streaming is often the cleanest solution in an environment where both ends speak gRPC.

streaming_patterns.go — all 4 patterns
Pattern 1: Unary — one request, one response
resp, err := client.Charge(ctx, req)

Pattern 2: Server streaming — one request, stream of responses
stream, _ := client.StreamTransactions(ctx, &pb.StreamRequest{UserId: "u1"})
for {
 tx, err := stream.Recv()
 if err == io.EOF { break } stream closed by server
 if err != nil { break }
 log.Printf("received tx: %s", tx.Id)
}

Pattern 3: Client streaming — stream of requests, one response
stream2, _ := client.BatchIngest(ctx)
for _, event := range events {
 stream2.Send(&pb.Event{Data: event})
}
resp2, _ := stream2.CloseAndRecv() server's single response
log.Printf("ingested: %d events", resp2.Count)

Pattern 4: Bidirectional streaming — both sides send independently
bistream, _ := client.Chat(ctx)
go func() {
 for msg := range outgoing {
 bistream.Send(&pb.Message{Text: msg})
 }
 bistream.CloseSend()
}()
for {
 in, err := bistream.Recv() server can push any time
 if err == io.EOF { break }
 display(in.Text)
}

06Why gRPC Doesn't Work in Browsers (and What To Do About It)

Here's one of the most frequent questions from engineers new to gRPC: if it's so much better than REST, why isn't every web application using it? The answer comes down to a fundamental browser constraint. gRPC requires direct access to HTTP/2 framing primitives — the ability to control stream IDs, trailers frames (which carry gRPC status codes), and flow control. Browsers intentionally expose HTTP as a higher-level abstraction (fetch API, XMLHttpRequest) that hides these primitives for security reasons. No browser exposes the raw HTTP/2 stream control that gRPC requires.

The workaround is gRPC-Web: a modified protocol that wraps gRPC calls in a format that can travel through normal browser HTTP requests. A proxy (Envoy, nginx, or a dedicated gRPC-Web proxy) sits between the browser and the gRPC server, translating gRPC-Web requests to native gRPC. It works, but with limitations: gRPC-Web doesn't support client streaming or full bidirectional streaming (because browsers can't initiate unbounded HTTP request streams), and the proxy adds latency and operational complexity.

The practical guidance: use REST or GraphQL for browser-to-server APIs. Use gRPC for server-to-server and mobile-to-server communication. This is precisely the architectural boundary Google, Netflix, and most large microservice deployments draw. The public-facing API is REST (or GraphQL), the internal mesh is gRPC. If you need real-time push in the browser, WebSockets or Server-Sent Events are better options than gRPC-Web for most use cases.

⚠️ Don't Force gRPC-Web Where REST Is Simpler

The temptation to use gRPC-Web for browser applications is understandable — you want one API contract for everything. Resist it unless you have specific performance requirements. gRPC-Web adds a proxy dependency, restricts streaming capabilities, and the developer experience for browser clients (debugging, tooling, human-readability of requests in DevTools) is significantly worse than REST+JSON. A better pattern: define your gRPC services for internal use, and use a thin REST-to-gRPC gateway (gRPC Transcoding via Envoy, or tools like grpc-gateway in Go) to expose a REST API for browser consumption automatically generated from your proto definitions.


07When to Use gRPC vs REST: The Decision Framework

The myth is that gRPC is universally superior and REST is legacy. The truth is that they optimize for different constraints. gRPC wins when performance, type safety, and multi-language support are the top priorities. REST wins when human readability, browser compatibility, third-party integrations, and developer familiarity are the top priorities.

The cases where gRPC clearly wins are three: high-frequency internal microservice communication (payment processing, inventory updates, recommendation scoring — operations happening thousands of times per second where the Protobuf binary encoding and HTTP/2 multiplexing provide measurable latency and throughput improvements), multi-language environments (your auth service is in Go, your ML inference is in Python, your mobile SDK is in Swift — gRPC's code generation means they all share the same typed contract without manual translation), and real-time data streaming (log aggregation, live telemetry, bidirectional chat — cases where gRPC's native streaming eliminates the need for a separate WebSocket infrastructure).

The cases where REST wins: public-facing APIs (REST's human-readable JSON is easier for third-party developers to understand, debug, and integrate; your API explorer, Postman collections, and SDK generation tooling all work seamlessly with REST), simple CRUD services with low traffic (the overhead of managing .proto files, code generation, and gRPC infrastructure isn't justified when your admin dashboard makes 10 RPS to a backend that has plenty of headroom), and any situation where the consumers are browsers (REST+JSON is the native language of the web). Many mature microservice architectures use both: gRPC for the internal service mesh, REST for the external API gateway layer.

✅ Start with gRPC Reflection for Debugging

One of REST's genuine advantages is debuggability: curl a URL, read JSON, understand what happened. gRPC's binary encoding makes this harder. The solution: enable gRPC server reflection in development and staging environments. Reflection lets tools like grpc_cli, Postman (gRPC support), and BloomRPC discover your service methods and message types at runtime — you don't need the .proto file to make test calls. In production, disable reflection (it's a security surface that exposes your API schema) but ensure your monitoring stack captures gRPC status codes, latency percentiles, and stream counts. Most observability platforms (Datadog, Prometheus+Grafana) have native gRPC metric integration.

LLM next token prediction probability distribution diagram showing vocabulary tokens with probability bars and sampling mechanism

synthesisHow It All Connects: The Complete gRPC Picture

gRPC is a coherent engineering design where every piece reinforces the others. Protocol Buffers provide compact binary encoding and a language-agnostic contract. The generated client stubs make that contract enforceable at compile time and effortless to consume. HTTP/2 provides multiplexed streams and efficient header compression. The four streaming patterns exploit HTTP/2's stream semantics to enable communication models REST can't efficiently support. And the deliberate trade-off of browser incompatibility is the price of the performance and streaming capabilities that make gRPC the right choice for internal microservice communication.

When you put it together: your microservices define their contracts in .proto files (versioned, reviewed as code, the source of truth). Code generation produces type-safe clients and servers in every language your organization uses. gRPC connections are long-lived and multiplexed, minimizing connection overhead. Deadlines propagate through the call chain, preventing cascade failures. Streaming patterns enable architectures that would require complex WebSocket infrastructure with REST. And at the boundary with the external world, a thin REST gateway translates your gRPC services to a browser-friendly API surface.


FAQFrequently Asked Questions

What is the difference between gRPC and REST? + REST uses HTTP/1.1 (typically), JSON encoding, and is resource-oriented (GET /users/123). gRPC uses HTTP/2, Protocol Buffer binary encoding, and is action-oriented (GetUser(id: 123)). Key practical differences: gRPC is significantly faster (binary encoding, multiplexed HTTP/2, no repeated headers), type-safe (compiled .proto contract enforces data types at build time), and multi-language friendly (one .proto file generates clients and servers in 10+ languages). REST is browser-native, human-readable, easier to debug with standard tools, and universally understood by third-party developers. Most production microservice architectures use gRPC for internal service-to-service communication and REST for external/public APIs. What are Protocol Buffers and how do they compare to JSON? + Protocol Buffers (Protobuf) is Google's binary serialization format. You define data structures in a .proto schema file; the protoc compiler generates data classes in your language of choice. Protobuf fields are identified by integer field numbers in the binary encoding (not repeated field name strings like JSON), and integers are encoded in variable-length format. The result: Protobuf messages are typically 3-10× smaller than equivalent JSON and significantly faster to serialize/deserialize. JSON advantages: human-readable, language-native (no compilation step), universally understood, and easy to inspect in logs and browser DevTools. Protobuf advantages: compact binary format, strongly typed schema, compile-time validation, and multi-language code generation from a single schema file. Why can't you use gRPC directly in a web browser? + gRPC requires direct control over HTTP/2 framing primitives — specifically the ability to set and read trailer frames (which carry gRPC status codes and metadata) and control stream flow at the byte level. Browsers expose HTTP as a high-level abstraction through fetch() and XMLHttpRequest, which intentionally hide these low-level primitives for security and simplicity. No browser gives JavaScript the access gRPC needs. The workaround is gRPC-Web, a modified protocol that wraps gRPC in a browser-compatible format, with a proxy (like Envoy) translating between gRPC-Web and native gRPC. gRPC-Web works but doesn't support client streaming or full bidirectional streaming, and the developer experience is inferior to native REST. For most web applications, REST or GraphQL is the better choice for browser-to-server communication. What are the 4 types of gRPC streaming patterns? + gRPC supports four RPC patterns: (1) Unary — one request, one response. Equivalent to a standard REST API call; covers most CRUD operations. (2) Server streaming — the client sends one request and receives a stream of responses. Used for real-time feeds, live updates, large result sets delivered progressively. (3) Client streaming — the client sends a stream of requests and receives one final response. Used for batch uploads, telemetry aggregation, large file transfers. (4) Bidirectional streaming — both client and server send streams of messages simultaneously and independently. Used for real-time chat, collaborative editing, control plane/data plane communication (like Envoy's xDS protocol). All four run over HTTP/2 streams, which is why gRPC requires HTTP/2 as its transport. How does gRPC handle errors and what are gRPC status codes? + gRPC uses a set of status codes distinct from HTTP status codes (though sent in HTTP/2 trailers). Key codes: OK (0) — success. CANCELLED (1) — client cancelled the call. INVALID_ARGUMENT (3) — bad request data. NOT_FOUND (5) — resource doesn't exist. ALREADY_EXISTS (6) — duplicate creation. RESOURCE_EXHAUSTED (8) — rate limit or quota exceeded. DEADLINE_EXCEEDED (4) — call took longer than the deadline. UNAVAILABLE (14) — server temporarily unavailable (safe to retry). INTERNAL (13) — server-side error. gRPC errors can include a status code plus an error message and optional structured error details (using google.rpc.Status). Always check for DEADLINE_EXCEEDED and UNAVAILABLE specifically — these indicate transient conditions where retry with exponential backoff is appropriate. How do I handle versioning in gRPC services? + gRPC and Protobuf have strong backward compatibility rules when followed. For safe backward-compatible changes: add new fields (existing clients ignore unknown fields), add new RPC methods (existing clients don't call them). For breaking changes that can't be avoided: use package versioning — define your service as package payment.v1 and package payment.v2, maintain both simultaneously, and migrate clients gradually. Never: remove a field number (binary decoders will misinterpret the binary for that field number), change a field's type, or rename a package (breaks generated code). The golden rule: field numbers are forever. Reserve removed field numbers with the reserved keyword. Use package-level versioning (payment.v2) for significant API redesigns rather than trying to evolve a single API indefinitely. Is gRPC secure by default? + gRPC supports TLS for transport security, but it's not always enabled by default in development setups (many examples use insecure credentials for simplicity). In production, always use TLS: configure server certificates and use credentials.NewTLS() on clients. gRPC also supports mutual TLS (mTLS) — where both client and server present certificates — which is common in service mesh environments (Istio, Linkerd automatically provision and rotate mTLS certificates between services). For authorization, gRPC interceptors (the equivalent of middleware) are used to validate JWT tokens, check ACLs, or enforce rate limits on every incoming RPC. Authentication and authorization are not built into gRPC itself — you implement them as interceptors, giving you full flexibility over the implementation. When should I use gRPC vs GraphQL for internal microservices? + gRPC and GraphQL solve different problems. gRPC is optimal for: direct service-to-service calls where you control both ends, binary performance is valuable (high RPS, bandwidth-constrained mobile), streaming is needed, or multi-language code generation from a single schema is important. GraphQL is optimal for: APIs consumed by multiple clients (web, iOS, Android) where different clients need different data shapes, avoiding over-fetching and under-fetching of data, and federated data access across multiple backend services. Many production architectures combine both: internal microservice mesh runs gRPC, the API gateway exposes a GraphQL API to clients that resolves queries by calling the appropriate internal gRPC services. This gives you the performance of gRPC internally and the flexibility of GraphQL externally.

📡 gRPC Signal Lab

Four experiments: Protobuf encoder, HTTP/2 stream visualizer, streaming patterns, and gRPC vs REST benchmark.

JSON → Protocol Buffers Encoder Input: JSON Message 0 B JSON size 0 B Protobuf size — Size reduction — Parse speedup est. Encoded Output Binary representation (hex) Field-by-field encoding

Each field encodes as: field_number<<3|wire_type + value. Strings: length-prefixed bytes. Integers: varint encoding (small ints = fewer bytes).

HTTP/2 vs HTTP/1.1 — watch streams multiplex over a single connection

HTTP/2 Stream Multiplexing

Simulate concurrent RPC calls. HTTP/1.1 queues them; HTTP/2 multiplexes over one connection.

Concurrent RPC calls 6 Response latency (ms) 200 — HTTP/1.1 total (ms) — HTTP/2 total (ms) — Speedup factor — TCP connections

Message flow visualization — arrows show direction and timing

Streaming Pattern Simulator Select pattern Pattern description 0 Messages — Flow direction

Throughput & latency comparison across scenarios

gRPC vs REST Benchmark Payload size (bytes) 500 Requests per second 1,000 Network bandwidth (Mb/s) 100 — REST p50 latency — gRPC p50 latency — REST bandwidth — gRPC bandwidth — Speed advantage — Winner
Tags
gRPCProtocol-BuffersHTTP/2microservicesstreamingREST-vs-gRPCclient-stubbidirectional-streamingProtobufAPI-design
Share this article