gRPC Cheatsheet

Server (Node)

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

Minimal Server

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);

// Handler implementations
function getUser(call, callback) {
  const { user_id } = call.request;
  const user = db.findUser(user_id);
  if (!user) {
    return callback({ code: grpc.status.NOT_FOUND, message: 'user not found' });
  }
  callback(null, { user });
}

// Build and start server
const server = new grpc.Server();
server.addService(proto.UserService.service, { getUser });
server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), (err, port) => {
  if (err) throw err;
  console.log(`Server running on port ${port}`);
});

Server Credentials

const fs = require('fs');

// Insecure (dev/internal only)
const creds = grpc.ServerCredentials.createInsecure();

// TLS (one-way — clients verify server cert)
const creds = grpc.ServerCredentials.createSsl(
  null,                                    // no CA — don't verify client cert
  [{
    private_key: fs.readFileSync('server.key'),
    cert_chain:  fs.readFileSync('server.crt'),
  }],
  false,                                   // checkClientCertificate
);

// mTLS (mutual — both sides present certs)
const creds = grpc.ServerCredentials.createSsl(
  fs.readFileSync('ca.crt'),               // root CA to verify clients
  [{
    private_key: fs.readFileSync('server.key'),
    cert_chain:  fs.readFileSync('server.crt'),
  }],
  true,                                    // require client certificate
);

Handler Signatures

// Unary
function getUser(call, callback) {
  // call.request      — the deserialized request message
  // call.metadata     — grpc.Metadata from client
  // call.cancelled    — boolean: was the call cancelled?
  // call.getPeer()    — string: client address
  // callback(error, response, trailer, flags)
  callback(null, { user: { id: 1, name: 'Alice' } });
}

// Server streaming
function listUsers(call) {
  // call.request      — single request message
  // call.write(msg)   — send one response chunk
  // call.end()        — signal end of stream
  // call.on('cancelled', cb) — client cancelled
  for (const user of db.getAllUsers()) {
    call.write({ user });
  }
  call.end();
}

// Client streaming
function uploadUsers(call, callback) {
  // call.on('data', cb)  — receive one chunk
  // call.on('end', cb)   — client done sending
  // call.on('error', cb) — stream error
  const users = [];
  call.on('data', (chunk) => users.push(chunk.user));
  call.on('end', () => {
    db.bulkInsert(users);
    callback(null, { count: users.length });
  });
}

// Bidirectional streaming
function syncUsers(call) {
  // call.on('data', cb)
  // call.on('end', cb)
  // call.write(msg)
  // call.end()
  call.on('data', (msg) => {
    const result = processSync(msg);
    call.write(result);
  });
  call.on('end', () => call.end());
}

Adding Multiple Services

server.addService(proto.UserService.service,  userHandlers);
server.addService(proto.OrderService.service, orderHandlers);
server.addService(proto.AdminService.service, adminHandlers);

Server Options

const server = new grpc.Server({
  'grpc.max_receive_message_length': 10 * 1024 * 1024,   // 10 MB
  'grpc.max_send_message_length':    10 * 1024 * 1024,
  'grpc.keepalive_time_ms':          30_000,
  'grpc.keepalive_timeout_ms':       10_000,
  'grpc.http2.max_pings_without_data': 0,                 // allow pings
  'grpc.http2.min_time_between_pings_ms': 10_000,
});

Sending Trailing Metadata

// Unary — pass trailer as third arg to callback
function getUser(call, callback) {
  const trailer = new grpc.Metadata();
  trailer.set('request-id', 'abc-123');
  callback(null, { user }, trailer);
}

// Streaming — sendMetadata / end with trailer
function listUsers(call) {
  const header = new grpc.Metadata();
  header.set('content-type', 'application/grpc');
  call.sendMetadata(header);            // send header metadata early

  for (const user of db.getAllUsers()) {
    call.write({ user });
  }

  const trailer = new grpc.Metadata();
  trailer.set('total-count', String(db.count()));
  call.end(trailer);                    // trailing metadata
}

Error Responses

// Unary — pass error object to callback
callback({
  code:    grpc.status.NOT_FOUND,
  message: 'user not found',
  details: 'No user with id 42',       // optional extra detail string
  metadata: new grpc.Metadata(),        // optional trailing metadata on error
});

// Streaming — emit error
call.emit('error', {
  code:    grpc.status.INTERNAL,
  message: 'database failure',
});
// OR destroy the writable side
call.destroy(new Error('something went wrong'));

Async Handler Pattern

// Wrap async handlers to catch unhandled rejections
function unaryHandler(impl) {
  return (call, callback) => {
    impl(call)
      .then((res) => callback(null, res))
      .catch((err) => {
        callback({
          code:    err.code ?? grpc.status.INTERNAL,
          message: err.message,
        });
      });
  };
}

async function getUserImpl(call) {
  const user = await db.getUser(call.request.user_id);
  if (!user) throw { code: grpc.status.NOT_FOUND, message: 'not found' };
  return { user };
}

server.addService(proto.UserService.service, {
  getUser: unaryHandler(getUserImpl),
});

Reflection (grpcurl / Postman support)

const { ReflectionService } = require('@grpc/reflection');
// npm install @grpc/reflection

const reflection = new ReflectionService(packageDef);
reflection.addToServer(server);
# Then use grpcurl without a proto file:
grpcurl -plaintext localhost:50051 list
grpcurl -plaintext localhost:50051 describe myapp.v1.UserService
grpcurl -plaintext -d '{"user_id":1}' localhost:50051 myapp.v1.UserService/GetUser

Health Checking

const { HealthImplementation } = require('grpc-health-check');
// npm install grpc-health-check

const statusMap = {
  '': 'SERVING',                          // overall health
  'myapp.v1.UserService': 'SERVING',
};

const healthImpl = new HealthImplementation(statusMap);
healthImpl.addToServer(server);

// Mark a service unhealthy at runtime
healthImpl.setStatus('myapp.v1.UserService', 'NOT_SERVING');
healthImpl.setStatus('myapp.v1.UserService', 'SERVING');

Graceful Shutdown

process.on('SIGTERM', () => {
  console.log('SIGTERM received — shutting down');
  server.tryShutdown((err) => {
    if (err) {
      console.error('shutdown error', err);
      server.forceShutdown();    // last resort
    }
    process.exit(0);
  });
});

tryShutdown stops accepting new connections and waits for in-flight RPCs to complete. forceShutdown terminates all connections immediately — use only as a last resort or after a timeout.

Server Interceptors

// Interceptors on the server side (as of @grpc/grpc-js 1.9+)
const { ServerInterceptingCall } = grpc;

function loggingInterceptor(methodDescriptor, call) {
  return new ServerInterceptingCall(call, {
    start(next) {
      console.log('RPC start:', methodDescriptor.path);
      next();
    },
    sendMessage(message, next) {
      next(message);
    },
    receiveMessage(next) {
      next();
    },
  });
}

const server = new grpc.Server({ interceptors: [loggingInterceptor] });

Common Patterns

// Health endpoint stub for Kubernetes liveness probe
// (use grpc-health-check package above OR a simple HTTP server)
const http = require('http');
http.createServer((_, res) => res.end('ok')).listen(8080);
// Read request-level peer address (for logging/rate-limiting)
function getUser(call, callback) {
  const peer = call.getPeer();   // e.g. "127.0.0.1:54321"
  logger.info({ peer, method: 'GetUser' });
  // ...
}