gRPC Cheatsheet

Metadata and Deadlines

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

Metadata Overview

gRPC metadata is a set of key-value pairs sent alongside a call, analogous to HTTP headers. There are two kinds:

KindSent byReceived via
Initial metadata (headers)Client with request; Server before first responsecall.on('metadata', cb) on client; call.metadata on server
Trailing metadata (trailers)Server after last responsecall.on('status', cb).metadata on client

Keys are strings (lowercase). Values are strings OR Buffer (for binary keys ending in -bin).

grpc.Metadata API

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

const meta = new grpc.Metadata();

// add — appends a value (multiple values allowed per key)
meta.add('authorization', 'Bearer eyJ...');
meta.add('x-roles', 'admin');
meta.add('x-roles', 'editor');   // same key, second value

// set — replaces all values for the key
meta.set('x-request-id', 'abc-123');

// get — returns array of values
meta.get('x-roles');              // ['admin', 'editor']
meta.get('x-request-id');        // ['abc-123']

// getMap — returns first value per key as a plain object
meta.getMap();                    // { authorization: 'Bearer ...', 'x-request-id': 'abc-123', 'x-roles': 'admin' }

// remove — deletes all values for a key
meta.remove('x-roles');

// clone — deep copy
const copy = meta.clone();

// merge — add all entries from another Metadata object
meta.merge(otherMeta);

Binary Metadata

// Key must end with -bin
const meta = new grpc.Metadata();

// Set binary value
meta.add('trace-context-bin', Buffer.from([0x00, 0x01, 0x02, 0x03]));

// Get binary value
const bufs = meta.get('trace-context-bin');   // Buffer[]

Binary metadata keys must end in -bin. All other key-value pairs must be US-ASCII strings.

Sending Metadata from the Client

const meta = new grpc.Metadata();
meta.add('authorization', 'Bearer ' + token);
meta.add('x-request-id', generateId());
meta.add('accept-language', 'en-US');

// Unary
client.getUser({ user_id: 1 }, meta, callback);

// Streaming
const stream = client.listItems(request, meta);

Sending Metadata from the Server

// Initial metadata — must be sent before the first call.write() or callback()
function getUser(call, callback) {
  const header = new grpc.Metadata();
  header.set('x-served-by', process.env.HOSTNAME);
  call.sendMetadata(header);    // sends immediately

  const user = db.find(call.request.user_id);
  callback(null, { user });
}

// Trailing metadata — sent with the final response
function getUser(call, callback) {
  const user = db.find(call.request.user_id);

  const trailer = new grpc.Metadata();
  trailer.set('x-db-query-ms', String(db.lastQueryMs));

  callback(null, { user }, trailer);   // 3rd arg = trailing metadata
}

// Server streaming — trailer in call.end()
function listItems(call) {
  for (const item of db.getAll()) call.write({ item });

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

Reading Metadata on the Client

// Unary
const call = client.getUser({ user_id: 1 }, (err, res) => {
  if (err) return console.error(err);
  console.log(res.user);
});

call.on('metadata', (headers) => {
  // Initial/header metadata from server
  console.log('x-served-by:', headers.get('x-served-by')[0]);
});

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

// Streaming — same events
const stream = client.listItems({});
stream.on('metadata', (headers) => { /* ... */ });
stream.on('status', (status) => { /* ... */ });
stream.on('data', (item) => { /* ... */ });
stream.on('end', () => { /* ... */ });

Reading Metadata on the Server

function getUser(call, callback) {
  // call.metadata is a grpc.Metadata object
  const authHeader = call.metadata.get('authorization');
  // authHeader is an Array: ['Bearer eyJ...']

  const token = authHeader[0]?.replace('Bearer ', '');
  if (!token) {
    return callback({ code: grpc.status.UNAUTHENTICATED, message: 'missing token' });
  }
  // ...
}

Per-Call Credentials (Auth Tokens)

// Attach per-call credentials at channel level (applies to all calls)
const callCreds = grpc.credentials.createFromMetadataGenerator((params, cb) => {
  const meta = new grpc.Metadata();
  meta.add('authorization', 'Bearer ' + getToken());
  cb(null, meta);
});

const channelCreds = grpc.credentials.combineChannelCredentials(
  grpc.credentials.createSsl(),
  callCreds,
);

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

// OR: inject per-call at call site (overrides channel-level for that call)
const meta = new grpc.Metadata();
meta.add('authorization', 'Bearer ' + specificToken);
client.getUser({ user_id: 1 }, meta, callback);

Deadlines

A deadline is an absolute point in time (a Date) by which the entire RPC must complete. It propagates automatically across service hops via gRPC's built-in deadline propagation.

// 5-second deadline
const deadline = new Date(Date.now() + 5_000);

// Apply to a unary call
client.getUser({ user_id: 1 }, new grpc.Metadata(), { deadline }, (err, res) => {
  if (err?.code === grpc.status.DEADLINE_EXCEEDED) {
    console.error('call timed out');
    return;
  }
  console.log(res.user);
});

// Apply to a streaming call
const stream = client.listItems({}, new grpc.Metadata(), { deadline });
stream.on('error', (err) => {
  if (err.code === grpc.status.DEADLINE_EXCEEDED) console.error('stream timed out');
});

Deadline vs Timeout

// Deadline — absolute Date (what gRPC uses internally)
const deadline = new Date(Date.now() + 5_000);

// Timeout helper — convert relative ms to absolute deadline
function msDeadline(ms) {
  return new Date(Date.now() + ms);
}

client.getUser({ user_id: 1 }, {}, { deadline: msDeadline(5_000) }, callback);

Service Config Default Timeout

// Set a default timeout per method via service config
// (applies when no per-call deadline is given)
const serviceConfig = JSON.stringify({
  methodConfig: [{
    name: [{ service: 'myapp.v1.UserService' }],
    timeout: '5s',    // default deadline for all methods in this service
  }],
});

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

Checking Remaining Deadline on Server

// grpc-js does not expose remaining deadline directly on call,
// but you can read the initial deadline from metadata (if client sends it explicitly)
// or rely on the framework to cancel the call when the deadline passes.

// The server call will receive 'cancelled' event when client deadline fires:
function slowHandler(call, callback) {
  const timer = setTimeout(() => {
    callback({ code: grpc.status.INTERNAL, message: 'slow operation done' });
  }, 10_000);

  call.on('cancelled', () => {
    clearTimeout(timer);
    console.log('client deadline passed, call cancelled');
  });
}

Metadata Propagation (Distributed Tracing)

// Pattern: propagate trace context through metadata
function buildTracingMetadata(parentCtx) {
  const meta = new grpc.Metadata();
  meta.add('x-trace-id',  parentCtx.traceId);
  meta.add('x-span-id',   parentCtx.spanId);
  meta.add('x-sampled',   '1');
  return meta;
}

// Server: extract, then forward to downstream services
function getUser(call, callback) {
  const traceId = call.metadata.get('x-trace-id')[0];
  const downstreamMeta = new grpc.Metadata();
  downstreamMeta.add('x-trace-id', traceId);
  downstreamMeta.add('x-span-id', newSpanId());

  downstreamClient.getProfile({ user_id: call.request.user_id }, downstreamMeta, callback);
}

Reserved Metadata Keys

KeyUsed by
content-typegRPC framing (always application/grpc)
tegRPC framing (trailers)
grpc-statusStatus code in trailers
grpc-messageStatus message in trailers
grpc-timeoutDeadline propagation
grpc-encodingMessage compression (gzip, identity)
grpc-accept-encodingSupported compression

Do not set grpc-* or content-type keys manually — the framework manages them.

Gotchas

meta.get() always returns an array — even for a single-value key. Use meta.get('key')[0] to get the first value.

Deadline is a Date, not a number{ deadline: 5000 } will be silently ignored; use new Date(Date.now() + 5000).

Trailing metadata on error — attach trailing metadata to the error object's metadata property, not via a separate callback argument: callback({ code: grpc.status.NOT_FOUND, message: '...', metadata: trailer }).

Binary keys — non -bin keys with Buffer values will throw a serialization error. Ensure binary data uses a key ending in -bin.