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); }); });
tryShutdownstops accepting new connections and waits for in-flight RPCs to complete.forceShutdownterminates 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' }); // ... }