Skip to content

gRPC — When & Why to Use It ​

IntermediateIntermediate1–3 yrs experience Builds on the foundations. Expect trade-off discussions, light math, and short code snippets.Prerequisites: Solid grasp of networking, databases, and at least one web framework.

What is gRPC? ​

gRPC (Google Remote Procedure Call) is a high-performance, open-source framework that lets one service call a function on another service — even if they run on different machines — as if it were a local function call.

Think of it as a super-charged alternative to REST APIs. Instead of sending JSON over HTTP, gRPC:

  • Uses Protocol Buffers (Protobuf) — a compact binary format (much smaller than JSON)
  • Runs over HTTP/2 — enabling multiplexing, streaming, and lower latency
  • Auto-generates client & server code in many languages from a schema file (.proto)

REST vs gRPC — The Core Difference ​

FeatureRESTgRPC
ProtocolHTTP/1.1HTTP/2
Data FormatJSON (text)Protobuf (binary)
ContractNone (implicit).proto schema (strict)
Code GenManualAutomatic
Streaming❌ (workaround)✅ Native
Browser Support✅ Native⚠️ Needs proxy (grpc-web)
Best forPublic APIs, CRUDInternal services, real-time

When Should You Use gRPC? ​

✅ USE gRPC when: ​

  • Microservices talking to each other — low latency, high throughput internal communication
  • Real-time streaming — live chat, stock tickers, video feeds, IoT sensor data
  • Polyglot environments — your services use different languages (Node.js + Go + Java) — gRPC generates clients for all of them
  • Strong contracts matter — you want compile-time errors if API shape changes, not runtime surprises
  • Performance is critical — Protobuf payloads are 3–10× smaller than JSON

❌ AVOID gRPC when: ​

  • Building public browser-facing APIs — browsers don't natively support gRPC (use REST or GraphQL instead)
  • Your team is small and the overhead of .proto files isn't worth it
  • You need human-readable request/response (debugging gRPC is harder without tooling)

Why is Debugging gRPC Harder? (Human-Readable vs. Binary) ​

One of the main trade-offs when choosing gRPC over REST is observability during debugging.

1. REST (Human-Readable) ​

When you make a REST API call, the data is sent as JSON (JavaScript Object Notation), which is just plain text. If a request fails, you can open your browser's Network tab or use a tool like cURL, and immediately read exactly what was sent and received.

Example REST Payload (JSON):

json
{
  "product_id": "SKU-789",
  "quantity": 3,
  "customer_id": "user-42"
}

Anyone can read this and instantly spot if quantity was accidentally sent as a string instead of a number.

2. gRPC (Binary / Protobuf) ​

gRPC uses Protocol Buffers (Protobuf) to serialize data. Instead of sending keys and values as text, Protobuf strips out the keys and converts the values into a highly compressed stream of bytes based on the .proto schema.

Example gRPC Payload (Binary):

text
\x0A\x07SKU-789\x10\x03\x1A\x07user-42

If you intercept this network traffic, it looks like gibberish. You cannot read it without a tool that knows the .proto schema to decode it.

Visualizing the Difference ​

The Solution: gRPC Tooling ​

To debug gRPC effectively, you cannot just use the standard browser network tab. You must use specialized tooling that can load your .proto files and translate the binary back into human-readable JSON for you:

  • Postman / BloomRPC: GUI clients that support importing .proto files to make and debug gRPC requests visually.
  • grpcurl: A command-line tool (like curl but for gRPC) that can read protobuf schemas and decode responses in the terminal.
  • gRPC UI: A browser-based GUI specifically for inspecting gRPC APIs.

The 4 Types of gRPC Communication ​


How gRPC Works — Architecture ​


Hands-On Example in JavaScript (Node.js) ​

Let's build a simple Order Service that a client calls to place an order.

Step 1 — Install dependencies ​

bash
npm install @grpc/grpc-js @grpc/proto-loader

Step 2 — Define the Contract (order.proto) ​

This is the schema file — the single source of truth for both client and server.

proto
syntax = "proto3";

package order;

// The service definition
service OrderService {
  // Unary RPC: client sends one request, gets one response
  rpc PlaceOrder (OrderRequest) returns (OrderResponse);

  // Server Streaming RPC: client sends one request, server streams responses
  rpc TrackOrder (TrackRequest) returns (stream TrackUpdate);
}

// Request message shape
message OrderRequest {
  string product_id = 1;
  int32 quantity = 2;
  string customer_id = 3;
}

// Response message shape
message OrderResponse {
  string order_id = 1;
  string status = 2;
  string message = 3;
}

message TrackRequest {
  string order_id = 1;
}

message TrackUpdate {
  string order_id = 1;
  string status = 2;
  string location = 3;
}

Step 3 — Build the Server (server.js) ​

js
const grpc = require("@grpc/grpc-js");
const protoLoader = require("@grpc/proto-loader");
const path = require("path");
const { v4: uuidv4 } = require("uuid");

// Load the .proto definition
const packageDef = protoLoader.loadSync(path.join(__dirname, "order.proto"), {
  keepCase: true,
  longs: String,
  enums: String,
  defaults: true,
  oneofs: true,
});

const orderProto = grpc.loadPackageDefinition(packageDef).order;

// --- Implement the service methods ---

/**
 * Unary RPC: PlaceOrder
 * Client sends one request → Server sends one response
 */
function placeOrder(call, callback) {
  const { product_id, quantity, customer_id } = call.request;

  console.log(
    `📦 New order from customer ${customer_id}: ${quantity}x ${product_id}`
  );

  // Simulate order processing
  const orderId = uuidv4();

  // callback(error, response)
  callback(null, {
    order_id: orderId,
    status: "CONFIRMED",
    message: `Order placed successfully! ID: ${orderId}`,
  });
}

/**
 * Server Streaming RPC: TrackOrder
 * Client sends one request → Server streams multiple updates
 */
function trackOrder(call) {
  const { order_id } = call.request;
  console.log(`🔍 Tracking order: ${order_id}`);

  const updates = [
    { status: "PROCESSING", location: "Warehouse" },
    { status: "SHIPPED", location: "Distribution Center" },
    { status: "OUT_FOR_DELIVERY", location: "Local Hub" },
    { status: "DELIVERED", location: "Customer Door" },
  ];

  let i = 0;
  const interval = setInterval(() => {
    if (i >= updates.length) {
      clearInterval(interval);
      call.end(); // Signal end of stream
      return;
    }
    // call.write() sends a chunk to the client
    call.write({ order_id, ...updates[i] });
    i++;
  }, 1000); // Send update every second
}

// --- Start the gRPC server ---
function main() {
  const server = new grpc.Server();

  server.addService(orderProto.OrderService.service, {
    PlaceOrder: placeOrder,
    TrackOrder: trackOrder,
  });

  const address = "0.0.0.0:50051";
  server.bindAsync(
    address,
    grpc.ServerCredentials.createInsecure(),
    (err, port) => {
      if (err) throw err;
      console.log(`🚀 gRPC Server running on port ${port}`);
    }
  );
}

main();

Step 4 — Build the Client (client.js) ​

js
const grpc = require("@grpc/grpc-js");
const protoLoader = require("@grpc/proto-loader");
const path = require("path");

// Load the same .proto file
const packageDef = protoLoader.loadSync(path.join(__dirname, "order.proto"), {
  keepCase: true,
  longs: String,
  enums: String,
  defaults: true,
  oneofs: true,
});

const orderProto = grpc.loadPackageDefinition(packageDef).order;

// Create a client stub pointing at the server
const client = new orderProto.OrderService(
  "localhost:50051",
  grpc.credentials.createInsecure()
);

// --- 1. Unary call: PlaceOrder ---
function placeOrder() {
  const request = {
    product_id: "SKU-789",
    quantity: 3,
    customer_id: "user-42",
  };

  console.log("📤 Placing order...");

  // client.PlaceOrder(request, callback)
  client.PlaceOrder(request, (error, response) => {
    if (error) {
      console.error("❌ Error:", error.message);
      return;
    }
    console.log("✅ Order placed!", response);

    // Now track the placed order using streaming
    trackOrder(response.order_id);
  });
}

// --- 2. Server Streaming call: TrackOrder ---
function trackOrder(orderId) {
  console.log(`\n📡 Tracking order ${orderId} (live stream)...`);

  // client.TrackOrder returns a readable stream
  const stream = client.TrackOrder({ order_id: orderId });

  // Listen for streamed messages
  stream.on("data", (update) => {
    console.log(`📍 [${update.status}] — ${update.location}`);
  });

  stream.on("end", () => {
    console.log("✅ Order tracking complete!");
  });

  stream.on("error", (err) => {
    console.error("❌ Stream error:", err.message);
  });
}

placeOrder();

Step 5 — Run It ​

bash
# Terminal 1 — Start the server
node server.js

# Terminal 2 — Run the client
node client.js

Expected output on client:

📤 Placing order...
✅ Order placed! { order_id: 'abc-123', status: 'CONFIRMED', message: 'Order placed successfully! ID: abc-123' }

📡 Tracking order abc-123 (live stream)...
📍 [PROCESSING] — Warehouse
📍 [SHIPPED] — Distribution Center
📍 [OUT_FOR_DELIVERY] — Local Hub
📍 [DELIVERED] — Customer Door
✅ Order tracking complete!

Full Architecture in a Microservices Setup ​

Key insight: The public-facing layer (browser/mobile) uses REST. Once inside your infrastructure, all service-to-service calls use gRPC for speed and type safety.


Error Handling in gRPC ​

gRPC has its own status codes (similar to HTTP status codes):

js
const grpc = require("@grpc/grpc-js");

// On the SERVER — return a structured error
function getProduct(call, callback) {
  const { product_id } = call.request;

  if (!product_id) {
    // Return a gRPC error with status code
    return callback({
      code: grpc.status.INVALID_ARGUMENT, // Like HTTP 400
      message: "product_id is required",
    });
  }

  const product = db.find(product_id);
  if (!product) {
    return callback({
      code: grpc.status.NOT_FOUND, // Like HTTP 404
      message: `Product ${product_id} not found`,
    });
  }

  callback(null, product);
}

// On the CLIENT — handle errors
client.GetProduct({ product_id: "XYZ" }, (error, response) => {
  if (error) {
    if (error.code === grpc.status.NOT_FOUND) {
      console.log("Product does not exist");
    } else if (error.code === grpc.status.UNAVAILABLE) {
      console.log("Service is down, retry later");
    }
    return;
  }
  console.log("Product:", response);
});

Common gRPC Status Codes ​

gRPC StatusHTTP EquivalentMeaning
OK200Success
INVALID_ARGUMENT400Bad input
NOT_FOUND404Resource missing
ALREADY_EXISTS409Duplicate
PERMISSION_DENIED403No access
UNAUTHENTICATED401Not logged in
UNAVAILABLE503Service down
INTERNAL500Server error
DEADLINE_EXCEEDED504Timeout

Decision Guide — REST or gRPC? ​


Summary ​

Details
WhatA framework for calling remote functions as if they're local
WhyFaster (binary Protobuf), strongly typed, supports streaming
WhenMicroservice-to-service communication, real-time, polyglot systems
When NOTPublic browser APIs, simple CRUD, small single-service apps
Key files.proto (schema), generated stubs (client & server code)
ProtocolHTTP/2 + Protocol Buffers

Released under the ISC License.