Back to all posts

RPC vs gRPC


In typical web development, we use REST APIs to allow the client (like a browser or mobile app) to talk to the server. It’s great for public APIs, CRUD apps, and simple systems.

But what about when servers need to talk to other servers?

That’s where RPC (Remote Procedure Call) comes in. It’s more direct and efficient way for services (like microservices) to communicate internally.

REST = great for frontend ↔ backend

RPC/gRPC = great for backend ↔ backend (especially in microservices)


In this post, we’ll break down:

  • What RPC and gRPC are
  • Why gRPC is becoming the go-to solution
  • A real-world example using Node.js and Next.js

Let’s dive in.

What is RPC?

RPC (Remote Procedure Call) is a way for programs to call functions on another computer as if they were local.

For example:

const result = remoteService.getUserData("abc123");

Looks like a local call — but the logic might be running on a server across the country. RPC hides the networking details from you. That’s both the strength and the trap.


What is gRPC?

gRPC, built by Google, is a modern take on RPC that’s:

  • Fast (uses HTTP/2 and binary encoding)
  • Language-agnostic
  • Automatically code-generating
  • Great for service-to-service communication

gRPC replaces typical JSON-over-HTTP REST calls with something more structured, type-safe, and efficient — ideal for microservices.

RPC vs gRPC at a Glance

FeatureClassic RPC / RESTgRPC
TransportHTTP 1.1HTTP/2
Data FormatJSON / XMLProtocol Buffers (Protobuf)
Code GenerationManual or OpenAPIAutomatic via protoc
StreamingNot commonFully supported (all directions)
Language SupportVariesWide (Go, Node.js, Python, etc.)
SpeedSlower, larger payloadsFaster, compact binary messages

Why Use gRPC?

Here’s why gRPC is gaining traction in modern backend systems:

Better performance: Protobufs are smaller and faster than JSON
Strict contracts: Protobuf definitions define the shape of all messages
Easy code sharing: gRPC generates client + server code from the same .proto file
Streaming support: Perfect for real-time data and efficient large-scale pipelines
Cross-language: Use gRPC with Go, Rust, Python, Java, or anything else


Real-World Example: Node.js gRPC Server + Next.js Client

Let’s build a basic app where:

  • A Node.js gRPC server manages orders
  • A Next.js frontend requests order status from that server

Project Structure

grpc-demo/
├── grpc-server/
│   └── server.js
├── proto/
│   └── order.proto
├── next-app/
│   └── pages/api/order.js

1. Define the Protobuf Contract

File: proto/order.proto

syntax = "proto3";

package order;

service OrderService {
  rpc GetOrderStatus (OrderRequest) returns (OrderResponse);
}

message OrderRequest {
  string orderId = 1;
}

message OrderResponse {
  string status = 1;
}

2. gRPC Server in Node.js

File: grpc-server/server.js

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

const PROTO_PATH = path.join(__dirname, '../proto/order.proto');
const packageDef = protoLoader.loadSync(PROTO_PATH);
const orderProto = grpc.loadPackageDefinition(packageDef).order;

function GetOrderStatus(call, callback) {
  const { orderId } = call.request;
  console.log(`Received request for order ${orderId}`);
  callback(null, { status: "Processing" });
}

function main() {
  const server = new grpc.Server();
  server.addService(orderProto.OrderService.service, { GetOrderStatus });
  server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), () => {
    console.log("gRPC server running on port 50051");
    server.start();
  });
}

main();

Install dependencies:

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

Run it:

node grpc-server/server.js

3. Next.js Client API Route

File: next-app/pages/api/order.js

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

const PROTO_PATH = path.resolve('./proto/order.proto');
const packageDefinition = protoLoader.loadSync(PROTO_PATH);
const orderProto = grpc.loadPackageDefinition(packageDefinition).order;

const client = new orderProto.OrderService(
  'localhost:50051',
  grpc.credentials.createInsecure()
);

export default function handler(req, res) {
  const { orderId } = req.query;

  client.GetOrderStatus({ orderId }, (err, response) => {
    if (err) {
      return res.status(500).json({ error: err.message });
    }
    res.status(200).json({ status: response.status });
  });
}

Test it in your browser:

http://localhost:3000/api/order?orderId=xyz123

You’ll get:

{
  "status": "Processing"
}

Let go though some terminology we just learnt.

Protobuf

Instead of using JSON, gRPC uses Protocol Buffers for defining and serializing messages.

Example Protobuf message:

message User {
  string name = 1;
  int32 age = 2;
}

Compared to JSON:

{ "name": "Alice", "age": 30 }

The Protobuf version is smaller, faster to transmit, and type-safe.

with this we can uset this for building bidirectional streams, NestJS integration, or JWT-secured gRPC APIs.