gRPC Cheatsheet
Unary Calls
Use this gRPC reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
What is a Unary Call
A unary RPC is the simplest pattern: the client sends one request and the server returns one response, just like an HTTP request/response.
service UserService {
rpc GetUser (GetUserRequest) returns (GetUserResponse);
}Server Handler
// handlers/user.js function getUser(call, callback) { const { user_id } = call.request; db.findById(user_id, (err, user) => { if (err) return callback({ code: grpc.status.INTERNAL, message: err.message }); if (!user) return callback({ code: grpc.status.NOT_FOUND, message: `user ${user_id} not found` }); callback(null, { user }); }); } // Async variant async function getUserAsync(call, callback) { try { const user = await db.findById(call.request.user_id); if (!user) return callback({ code: grpc.status.NOT_FOUND, message: 'not found' }); callback(null, { user }); } catch (err) { callback({ code: grpc.status.INTERNAL, message: err.message }); } }
Client — Callback Style
const meta = new grpc.Metadata(); meta.add('x-request-id', 'req-001'); const deadline = new Date(Date.now() + 3000); // 3 s client.getUser( { user_id: 42 }, // request meta, // optional metadata (can omit) { deadline }, // optional options (can omit) (err, response) => { if (err) { console.error(err.code, err.message); return; } console.log(response.user); }, );
Argument Overloads
// All of these are valid: client.getUser(request, callback) client.getUser(request, metadata, callback) client.getUser(request, options, callback) client.getUser(request, metadata, options, callback)
Client — Promise Style
// util.promisify const { promisify } = require('util'); const getUser = promisify(client.getUser.bind(client)); try { const response = await getUser({ user_id: 42 }); console.log(response.user); } catch (err) { console.error(err.code, err.message); }
// Manual wrapper (includes metadata + deadline) function rpc(method, request, { metadata, deadlineMs } = {}) { return new Promise((resolve, reject) => { const meta = metadata ?? new grpc.Metadata(); const opts = deadlineMs ? { deadline: new Date(Date.now() + deadlineMs) } : {}; method.call(client, request, meta, opts, (err, res) => { if (err) reject(err); else resolve(res); }); }); } const response = await rpc(client.getUser, { user_id: 42 }, { deadlineMs: 3000 });
Cancellation
const call = client.getUser({ user_id: 42 }, (err, res) => { if (err?.code === grpc.status.CANCELLED) { return console.log('call was cancelled'); } console.log(res.user); }); // Cancel after 1 second const timer = setTimeout(() => call.cancel(), 1000); // Clear the timer if call completes normally // (the callback handles both paths)
Reading Header and Trailer Metadata
const call = client.getUser({ user_id: 42 }, (err, res) => { if (err) return console.error(err); console.log(res.user); }); call.on('metadata', (headers) => { console.log('initial headers:', headers.getMap()); }); call.on('status', (status) => { console.log('status code:', status.code); console.log('trailing metadata:', status.metadata.getMap()); });
Server — Sending Trailing Metadata
function getUser(call, callback) { const user = db.findById(call.request.user_id); const trailer = new grpc.Metadata(); trailer.set('x-served-by', process.env.HOSTNAME); // 3rd arg to callback = trailing metadata callback(null, { user }, trailer); }
Server — Sending Initial Metadata Early
// For unary calls sendMetadata must be called BEFORE callback function getUser(call, callback) { const header = new grpc.Metadata(); header.set('x-request-id', generateId()); call.sendMetadata(header); // sends immediately const user = db.findById(call.request.user_id); callback(null, { user }); }
Error Object Reference
// Shape of the error passed to callback (on error path) { code: grpc.status.NOT_FOUND, // numeric status code message: 'user not found', // short human-readable string details: 'no row in users table', // optional extended detail metadata: new grpc.Metadata(), // optional trailing metadata }
| Status | When to use |
|---|---|
NOT_FOUND | Entity does not exist |
INVALID_ARGUMENT | Client sent bad input |
ALREADY_EXISTS | Create conflicts with existing record |
PERMISSION_DENIED | Authenticated but not authorized |
UNAUTHENTICATED | Missing or invalid credentials |
RESOURCE_EXHAUSTED | Rate limit hit |
INTERNAL | Unexpected server-side error |
UNAVAILABLE | Server not ready — safe to retry |
DEADLINE_EXCEEDED | Operation timed out |
Full Round-Trip Example
// user.proto syntax = "proto3"; package myapp.v1; service UserService { rpc GetUser (GetUserRequest) returns (GetUserResponse); } message GetUserRequest { int32 user_id = 1; } message GetUserResponse { User user = 1; } message User { int32 id = 1; string name = 2; string email = 3; }
// server.js const grpc = require('@grpc/grpc-js'); const protoLoader = require('@grpc/proto-loader'); const def = protoLoader.loadSync('./user.proto', { keepCase: true, longs: String, enums: String, defaults: true, oneofs: true }); const { myapp: { v1: proto } } = grpc.loadPackageDefinition(def); const db = { 1: { id: 1, name: 'Alice', email: 'alice@example.com' } }; function getUser({ request }, callback) { const user = db[request.user_id]; if (!user) return callback({ code: grpc.status.NOT_FOUND, message: 'not found' }); callback(null, { user }); } const server = new grpc.Server(); server.addService(proto.UserService.service, { getUser }); server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), () => { console.log('listening on 50051'); });
// client.js const grpc = require('@grpc/grpc-js'); const protoLoader = require('@grpc/proto-loader'); const { promisify } = require('util'); const def = protoLoader.loadSync('./user.proto', { keepCase: true, longs: String, enums: String, defaults: true, oneofs: true }); const { myapp: { v1: proto } } = grpc.loadPackageDefinition(def); const client = new proto.UserService('localhost:50051', grpc.credentials.createInsecure()); const getUser = promisify(client.getUser.bind(client)); (async () => { try { const { user } = await getUser({ user_id: 1 }); console.log(user); // { id: 1, name: 'Alice', email: 'alice@example.com' } } catch (err) { console.error(err.code, err.message); } finally { client.close(); } })();
Gotchas
Calling
callbacktwice — calling the callback more than once in a unary handler causes a crash. Guard every early-return path carefully.
async handlers without try/catch — an uncaught rejection will not invoke the callback; the client will hang until its deadline. Always wrap async server handlers.
Metadata before first call — if the client has no
waitForReadyguard and the server is not yet up, the first call getsUNAVAILABLEimmediately (no retry by default unless a retry policy is configured).