Streaming RPCs

gRPC · Practical · 15 min

Streaming replaces polling and huge responses with incremental, flow-controlled messages.

In grpc-js, streaming calls are Node.js streams in object mode:

  • Server streaming: the handler receives a writable call; it write()s messages and calls end(). The client gets a readable stream: listen for data, end, error and status, or use for await.
  • Client streaming: the handler reads call as a readable stream and answers once via callback. The client gets a writable stream plus a callback.
  • Bidirectional: both sides are duplex streams that read and write independently.

Things to get right:

  • Respect backpressure: call.write() returns false when the buffer is full; wait for drain (or use pipeline) before writing more.
  • Handle cancelled on the server: if the client goes away, stop producing work.
  • A stream ends with a single status. Errors mid-stream terminate the whole call; there is no per-message error.
  • Long-lived streams need keepalives and reconnection logic on the client, because connections do drop.

Streams are ordered within a call, but there is no ordering between calls. Prefer unary RPCs with pagination for simple bounded lists; use streaming for real-time feeds, large transfers, or chatty interactive protocols.

Example

// hello.proto adds: rpc CountDown (CountRequest) returns (stream Tick);
//   message CountRequest { int32 from = 1; }  message Tick { int32 n = 1; }
import * as grpc from "@grpc/grpc-js";
import * as protoLoader from "@grpc/proto-loader";
const { hello } = grpc.loadPackageDefinition(protoLoader.loadSync("hello.proto"));

const server = new grpc.Server();
server.addService(hello.v1.Greeter.service, {
  CountDown: (call) => {
    for (let n = call.request.from; n > 0; n--) call.write({ n });
    call.end();
  },
});
server.bindAsync("127.0.0.1:50053", grpc.ServerCredentials.createInsecure(), async () => {
  const client = new hello.v1.Greeter("127.0.0.1:50053", grpc.credentials.createInsecure());
  const ticks = [];
  for await (const tick of client.CountDown({ from: 3 })) ticks.push(tick.n);
  console.log(ticks); // [ 3, 2, 1 ]
  server.forceShutdown();
});

Remember

  • grpc-js streaming calls are Node object-mode streams
  • Respect backpressure: honour write() returning false
  • One status ends the stream; mid-stream errors end the call
  • Watch for cancelled on the server side

Watch out for

  • Ignoring client cancellation and continuing to compute and write results nobody reads.

Check yourself

  1. In a server-streaming handler, how does the server signal it has sent all messages?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: grpc.io/docs/what-is-grpc/core-concepts/#server-streaming-rpc