gRPC Cheatsheet

Client

Use this gRPC reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

Creating a Client Stub

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

const packageDef = protoLoader.loadSync('./protos/user.proto', {
  keepCase: true,
  longs: String,
  enums: String,
  defaults: true,
  oneofs: true,
});
const { myapp: { v1: proto } } = grpc.loadPackageDefinition(packageDef);

// The stub wraps a channel; all methods are auto-generated
const client = new proto.UserService(
  'localhost:50051',
  grpc.credentials.createInsecure(),
);

Client Credentials

// Insecure (plaintext)
const creds = grpc.credentials.createInsecure();

// TLS with system CA roots
const creds = grpc.credentials.createSsl();

// TLS with custom CA
const creds = grpc.credentials.createSsl(fs.readFileSync('ca.crt'));

// mTLS
const creds = grpc.credentials.createSsl(
  fs.readFileSync('ca.crt'),
  fs.readFileSync('client.key'),
  fs.readFileSync('client.crt'),
);

// TLS + per-call token (combined)
const tlsCreds  = grpc.credentials.createSsl();
const callCreds = grpc.credentials.createFromMetadataGenerator((params, cb) => {
  const meta = new grpc.Metadata();
  meta.add('authorization', `Bearer ${getAccessToken()}`);
  cb(null, meta);
});
const combined = grpc.credentials.combineChannelCredentials(tlsCreds, callCreds);

const client = new proto.UserService('api.example.com:443', combined);

Channel Options

const client = new proto.UserService('localhost:50051', creds, {
  'grpc.max_receive_message_length':  4 * 1024 * 1024,
  'grpc.max_send_message_length':     4 * 1024 * 1024,
  'grpc.keepalive_time_ms':           30_000,
  'grpc.keepalive_timeout_ms':        10_000,
  'grpc.keepalive_permit_without_calls': 1,
  'grpc.enable_retries':              1,
  // Load balancing
  'grpc.service_config': JSON.stringify({
    loadBalancingConfig: [{ round_robin: {} }],
  }),
});

Deadlines

// Deadline is an absolute Date object (not a duration)
const deadline = new Date(Date.now() + 5000);   // 5 seconds from now

client.getUser({ user_id: 1 }, { deadline }, (err, response) => {
  if (err?.code === grpc.status.DEADLINE_EXCEEDED) {
    console.error('timed out');
    return;
  }
  console.log(response);
});

Metadata (Headers)

const meta = new grpc.Metadata();
meta.add('authorization', 'Bearer eyJhbG...');
meta.add('x-request-id', 'abc-123');
meta.set('x-single-value', 'only-one');        // set replaces all values for the key

// Binary metadata — key must end with -bin
meta.add('trace-bin', Buffer.from([0x01, 0x02]));

// Pass as second arg (before callback / options)
client.getUser({ user_id: 1 }, meta, (err, res) => { /* ... */ });

Promisify a Unary Call

const { promisify } = require('util');

// Promisify individual methods
const getUser = promisify(client.getUser.bind(client));
const user = await getUser({ user_id: 1 });

// Generic wrapper
function callAsync(method, request, metadata = new grpc.Metadata()) {
  return new Promise((resolve, reject) => {
    method.call(client, request, metadata, (err, res) => {
      if (err) reject(err);
      else resolve(res);
    });
  });
}

const res = await callAsync(client.getUser, { user_id: 1 });

Cancelling a Call

// Every call returns a ClientUnaryCall / ClientReadableStream / etc.
// All have .cancel()
const call = client.getUser({ user_id: 1 }, (err, res) => {
  if (err?.code === grpc.status.CANCELLED) {
    console.log('cancelled by client');
    return;
  }
  console.log(res);
});

setTimeout(() => call.cancel(), 1000);   // cancel after 1s

Reading Metadata from Responses

const call = client.getUser({ user_id: 1 }, (err, res) => {
  console.log(res);
});

call.on('metadata', (metadata) => {
  // initial headers from server
  console.log('header request-id:', metadata.get('request-id'));
});

call.on('status', (status) => {
  // final status + trailing metadata
  console.log('trailing metadata:', status.metadata.getMap());
});

Wait for Ready

// waitForReady — useful after creating client before first call
// deadline: Date object
client.waitForReady(new Date(Date.now() + 10_000), (err) => {
  if (err) throw err;
  console.log('channel is ready');
});

Channel State

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

const state = client.getChannel().getConnectivityState(true); // true = try to connect
// States: IDLE(0) CONNECTING(1) READY(2) TRANSIENT_FAILURE(3) SHUTDOWN(4)

client.getChannel().watchConnectivityState(state, deadline, (err) => {
  // fires when state changes or deadline passes
  const newState = client.getChannel().getConnectivityState(false);
  console.log('new state:', newState);
});

Closing the Client

// Closes the underlying channel — no more calls possible
client.close();

Load Balancing

// DNS round-robin (multiple A records)
const client = new proto.UserService('dns:///user-service.default.svc.cluster.local:50051', creds, {
  'grpc.service_config': JSON.stringify({
    loadBalancingConfig: [{ round_robin: {} }],
  }),
});

// Multiple addresses (pick_first by default)
// Use xDS / service mesh for production load balancing

Retry Policy

const serviceConfig = {
  methodConfig: [
    {
      name: [{ service: 'myapp.v1.UserService', method: 'GetUser' }],
      retryPolicy: {
        maxAttempts: 4,              // includes first attempt
        initialBackoff: '0.1s',
        maxBackoff: '1s',
        backoffMultiplier: 2.0,
        retryableStatusCodes: ['UNAVAILABLE', 'RESOURCE_EXHAUSTED'],
      },
    },
    {
      name: [{ service: 'myapp.v1.UserService' }],  // wildcard: all methods
      timeout: '5s',                                  // default deadline
    },
  ],
};

const client = new proto.UserService('localhost:50051', creds, {
  'grpc.enable_retries': 1,
  'grpc.service_config': JSON.stringify(serviceConfig),
});

Hedging Policy

// Hedging sends the RPC to multiple backends simultaneously
// and uses the first successful response
const serviceConfig = {
  methodConfig: [{
    name: [{ service: 'myapp.v1.UserService', method: 'GetUser' }],
    hedgingPolicy: {
      maxAttempts: 3,
      hedgingDelay: '0.05s',         // 50ms before sending next hedge
      nonFatalStatusCodes: ['UNAVAILABLE'],
    },
  }],
};

Common Gotchas

Channel reuse — create one client (channel) per service and reuse it for the lifetime of the process. Creating a new client per request is expensive and leaks channels.

Deadline is a Date, not a durationnew Date(Date.now() + 5000) not 5000.

Error code on timeoutDEADLINE_EXCEEDED when the client-side deadline fires; CANCELLED when you call .cancel().

waitForReady — without this, calls made immediately after construction may fail with UNAVAILABLE if the server is not yet reachable. Use in service startup health checks.

Binary metadata keys — must end with -bin; values are Buffer. All other keys are strings.