Backend Development6 min read

gRPC & Protocol Buffers

Build efficient service-to-service APIs with gRPC and Protocol Buffers: unary RPC, streaming, code generation, and service mesh integration.

grpcprotobufmicroservicesapi

gRPC is a high-performance RPC framework using Protocol Buffers for schema definition and binary serialization. It's 5-10× faster than REST+JSON for service-to-service communication and provides strongly-typed, code-generated clients and servers across languages.

Use gRPC for internal service-to-service communication where performance and type safety matter. Use REST for public APIs (browser compatibility). Protocol Buffers are your contract - treat them with the same care as database schemas.

Workflow

  1. Define services with Protocol BuffersProtobuf schemas are the single source of truth - they generate clients and servers in any language.
  2. Generate code with bufManual protoc compilation is complex; buf provides a modern, reproducible build tool with a registry.
  3. Implement servers and clients in Node.jsCode-generated stubs eliminate manual serialization and provide type-safe method calls.
  4. Implement streaming RPCsStreaming enables pushing large datasets or real-time updates without polling.
  5. Add interceptors for cross-cutting concernsInterceptors implement auth, logging, metrics, and retry without polluting service logic.

Output Format

Protobuf schema with messages and services, buf generate for code gen, Connect-based server implementation, streaming RPCs, auth interceptor.

Examples

Proto definition: `service ProductService { rpc GetProduct(GetProductRequest) returns (Product); rpc SearchProducts(SearchRequest) returns (stream Product); }; message Product { string id = 1; string name = 2; float price = 3; bool in_stock = 4; }`
Connect server: `router.service(ProductService, { async getProduct({ id }) { const p = await db.products.findById(id); if (!p) throw new ConnectError("Not found", Code.NotFound); return new Product(p); } })`
Streaming: `async *searchProducts({ query }) { const cursor = db.products.findAll({ name: { contains: query } }); for await (const p of cursor) yield new Product(p) }`

Edge Cases & Gotchas

  • Field number reuse: reusing a deleted field number with a different type corrupts deserialization of old data - always reserve deleted field numbers with `reserved 5, 6`
  • gRPC-Web: browsers can't use gRPC directly (HTTP/2 framing); use gRPC-Web or ConnectRPC (HTTP/1.1 compatible) for browser clients
  • Deadline propagation: always set deadlines on client calls; propagate them through to downstream calls to prevent cascading hangs

When to Break These Rules

  • For public APIs that must support curl, Postman, or browser fetch without special setup, REST+JSON is more accessible. Use gRPC at the service boundary, REST at the public boundary.

Reference Files

  • ConnectRPC: https://connectrpc.com/
  • buf tool: https://buf.build/
  • Protocol Buffers: https://protobuf.dev/
  • gRPC docs: https://grpc.io/docs/

More skills in this library

Browse all technical skills - Mobile, AI/ML, Web, Backend, Design, and Product.