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:
| Kind | Sent by | Received via |
|---|---|---|
| Initial metadata (headers) | Client with request; Server before first response | call.on('metadata', cb) on client; call.metadata on server |
| Trailing metadata (trailers) | Server after last response | call.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
| Key | Used by |
|---|---|
content-type | gRPC framing (always application/grpc) |
te | gRPC framing (trailers) |
grpc-status | Status code in trailers |
grpc-message | Status message in trailers |
grpc-timeout | Deadline propagation |
grpc-encoding | Message compression (gzip, identity) |
grpc-accept-encoding | Supported compression |
Do not set
grpc-*orcontent-typekeys manually — the framework manages them.
Gotchas
meta.get()always returns an array — even for a single-value key. Usemeta.get('key')[0]to get the first value.
Deadline is a
Date, not a number —{ deadline: 5000 }will be silently ignored; usenew Date(Date.now() + 5000).
Trailing metadata on error — attach trailing metadata to the error object's
metadataproperty, not via a separatecallbackargument:callback({ code: grpc.status.NOT_FOUND, message: '...', metadata: trailer }).
Binary keys — non
-binkeys withBuffervalues will throw a serialization error. Ensure binary data uses a key ending in-bin.