Advanced
30 min
Full Guide

gRPC & Protocol Buffers

Learn gRPC for high-performance, typed RPC communication with Protocol Buffers

What is gRPC?

gRPC (gRPC Remote Procedure Call) is a high-performance, open-source RPC framework developed by Google. It uses Protocol Buffers (protobuf) for serialization and HTTP/2 for transport, making it significantly faster than REST APIs for many use cases.

gRPC is ideal for microservices communication, real-time streaming, and scenarios where performance and type safety are critical.

⚡ gRPC vs REST

REST

  • • JSON/XML - Human readable
  • • HTTP/1.1 - Text-based
  • • Request/Response only
  • • Loosely typed
  • • Browser native support

gRPC

  • • Protobuf - Binary, compact
  • • HTTP/2 - Multiplexed streams
  • • Streaming support
  • • Strongly typed contracts
  • • 10x faster serialization

Protocol Buffers (Protobuf)

Protocol Buffers define the structure of your data and services:

// user.proto - Protocol Buffer definition
syntax = "proto3";

package user;

// Message types (like TypeScript interfaces)
message User {
  int32 id = 1;
  string name = 2;
  string email = 3;
  UserRole role = 4;
  repeated string tags = 5;  // Array
  optional string bio = 6;   // Optional field
}

enum UserRole {
  USER_ROLE_UNSPECIFIED = 0;
  USER_ROLE_ADMIN = 1;
  USER_ROLE_MEMBER = 2;
}

message GetUserRequest {
  int32 id = 1;
}

message GetUsersRequest {
  int32 page = 1;
  int32 limit = 2;
}

message UserList {
  repeated User users = 1;
  int32 total = 2;
}

message CreateUserRequest {
  string name = 1;
  string email = 2;
  UserRole role = 3;
}

// Service definition (like REST endpoints)
service UserService {
  // Unary RPC (request-response)
  rpc GetUser(GetUserRequest) returns (User);
  rpc CreateUser(CreateUserRequest) returns (User);
  
  // Server streaming
  rpc ListUsers(GetUsersRequest) returns (stream User);
  
  // Client streaming
  rpc UploadUsers(stream User) returns (UserList);
  
  // Bidirectional streaming
  rpc Chat(stream Message) returns (stream Message);
}

gRPC Communication Patterns

1. Unary RPC

Single request, single response. Like REST.

Client → Request → Server
Client ← Response ← Server

2. Server Streaming

Single request, stream of responses.

Client → Request → Server
Client ← Response 1 ← Server
Client ← Response 2 ← Server
Client ← Response N ← Server

3. Client Streaming

Stream of requests, single response.

Client → Request 1 → Server
Client → Request 2 → Server
Client → Request N → Server
Client ← Response ← Server

4. Bidirectional Streaming

Both sides stream independently.

Client ↔ Server
(Both send/receive anytime)

gRPC-Web for Browsers

Browsers can't use gRPC directly. Use gRPC-Web with a proxy:

// Install gRPC-Web
// npm install grpc-web

// Generated client from .proto file
import { UserServiceClient } from './generated/user_grpc_web_pb';
import { GetUserRequest, User } from './generated/user_pb';

// Create client (connects to Envoy proxy)
const client = new UserServiceClient('https://api.example.com');

// Unary call
async function getUser(id: number): Promise {
  const request = new GetUserRequest();
  request.setId(id);
  
  return new Promise((resolve, reject) => {
    client.getUser(request, {}, (err, response) => {
      if (err) {
        reject(err);
      } else {
        resolve(response);
      }
    });
  });
}

// Server streaming
function listUsers() {
  const request = new GetUsersRequest();
  request.setPage(1);
  request.setLimit(10);
  
  const stream = client.listUsers(request, {});
  
  stream.on('data', (user: User) => {
    console.log('Received user:', user.toObject());
  });
  
  stream.on('error', (err) => {
    console.error('Stream error:', err);
  });
  
  stream.on('end', () => {
    console.log('Stream ended');
  });
}

// Usage
const user = await getUser(123);
console.log(user.getName(), user.getEmail());

Node.js gRPC Server

// server.js - gRPC server in Node.js
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');

// Load proto file
const packageDefinition = protoLoader.loadSync('user.proto', {
  keepCase: true,
  longs: String,
  enums: String,
  defaults: true,
  oneofs: true
});

const userProto = grpc.loadPackageDefinition(packageDefinition).user;

// In-memory database
const users = new Map();
let nextId = 1;

// Service implementation
const userService = {
  // Unary RPC
  getUser(call, callback) {
    const user = users.get(call.request.id);
    if (user) {
      callback(null, user);
    } else {
      callback({
        code: grpc.status.NOT_FOUND,
        message: 'User not found'
      });
    }
  },

  // Unary RPC
  createUser(call, callback) {
    const user = {
      id: nextId++,
      name: call.request.name,
      email: call.request.email,
      role: call.request.role || 'USER_ROLE_MEMBER'
    };
    users.set(user.id, user);
    callback(null, user);
  },

  // Server streaming
  listUsers(call) {
    const { page, limit } = call.request;
    const allUsers = Array.from(users.values());
    const start = (page - 1) * limit;
    const pageUsers = allUsers.slice(start, start + limit);
    
    // Stream each user
    for (const user of pageUsers) {
      call.write(user);
    }
    
    call.end();
  },

  // Client streaming
  uploadUsers(call, callback) {
    const uploadedUsers = [];
    
    call.on('data', (user) => {
      user.id = nextId++;
      users.set(user.id, user);
      uploadedUsers.push(user);
    });
    
    call.on('end', () => {
      callback(null, {
        users: uploadedUsers,
        total: uploadedUsers.length
      });
    });
  }
};

// Start server
const server = new grpc.Server();
server.addService(userProto.UserService.service, userService);

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

Node.js gRPC Client

// client.js - gRPC client in Node.js
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');

const packageDefinition = protoLoader.loadSync('user.proto');
const userProto = grpc.loadPackageDefinition(packageDefinition).user;

// Create client
const client = new userProto.UserService(
  'localhost:50051',
  grpc.credentials.createInsecure()
);

// Promisify for async/await
function promisify(method) {
  return (request) => new Promise((resolve, reject) => {
    method.call(client, request, (err, response) => {
      if (err) reject(err);
      else resolve(response);
    });
  });
}

const getUser = promisify(client.getUser);
const createUser = promisify(client.createUser);

// Usage
async function main() {
  // Create user
  const newUser = await createUser({
    name: 'John Doe',
    email: 'john@example.com',
    role: 'USER_ROLE_ADMIN'
  });
  console.log('Created:', newUser);

  // Get user
  const user = await getUser({ id: newUser.id });
  console.log('Retrieved:', user);

  // Server streaming
  const stream = client.listUsers({ page: 1, limit: 10 });
  
  stream.on('data', (user) => {
    console.log('Streamed user:', user);
  });
  
  stream.on('end', () => {
    console.log('Stream complete');
  });
}

main().catch(console.error);

gRPC with Connect (Modern Alternative)

// Connect is a modern gRPC-compatible framework
// Works in browsers without a proxy!

// npm install @connectrpc/connect @connectrpc/connect-web

import { createPromiseClient } from '@connectrpc/connect';
import { createConnectTransport } from '@connectrpc/connect-web';
import { UserService } from './generated/user_connect';

// Create transport
const transport = createConnectTransport({
  baseUrl: 'https://api.example.com',
});

// Create typed client
const client = createPromiseClient(UserService, transport);

// Fully typed, async/await friendly!
async function main() {
  // Unary call
  const user = await client.getUser({ id: 123 });
  console.log(user.name, user.email);

  // Create user
  const newUser = await client.createUser({
    name: 'Jane Doe',
    email: 'jane@example.com',
    role: 'USER_ROLE_MEMBER'
  });

  // Server streaming
  for await (const user of client.listUsers({ page: 1, limit: 10 })) {
    console.log('Received:', user.name);
  }
}

main();

When to Use gRPC

✅ Use gRPC When

  • • Microservices communication
  • • High-performance needed
  • • Real-time streaming
  • • Polyglot environments
  • • Strong typing required
  • • Mobile apps (bandwidth matters)

⚠️ Consider REST When

  • • Public APIs (wider compatibility)
  • • Browser-first applications
  • • Simple CRUD operations
  • • Team unfamiliar with gRPC
  • • Need human-readable debugging

💡 gRPC Best Practices

  • Version your protos - Use package versioning (v1, v2)
  • Use field numbers carefully - Never reuse or change them
  • Implement deadlines - Always set timeouts on calls
  • Handle streaming errors - Streams can fail mid-transfer
  • Use gRPC-Web or Connect - For browser compatibility
  • Monitor with interceptors - Add logging and metrics