Skip to content

RPC Types in gRPC — Complete Guide

DodaTech Updated 2026-06-28 3 min read

In this tutorial, you will learn about RPC Types in gRPC. We cover key concepts, practical examples, and best practices to help you master this topic.

gRPC supports four RPC types based on streaming patterns. Unary RPC is a single request-response. Server-streaming sends multiple responses. Client-streaming receives multiple requests. Bidirectional streaming handles concurrent streams both ways.

What You'll Learn

  • Unary RPC: single request, single response
  • Server-streaming: single request, stream of responses
  • Client-streaming: stream of requests, single response
  • Bidirectional streaming: concurrent streams
  • When to choose each RPC type

Why It Matters

Choosing the right RPC type impacts latency, throughput, and client complexity. Server-streaming is ideal for large datasets. Client-streaming suits batch uploads. Bidirectional streaming enables real-time scenarios.

Real-World Use

etcd uses unary for key-value puts, server-streaming for watch notifications, client-streaming for Transaction batches, and bidirectional for lease keep-alives. Google Maps uses server-streaming for route updates.

flowchart LR
    subgraph Unary [Unary RPC]
        UC[Client] -->|Single Request| US[Server]
        US -->|Single Response| UC
    end
    subgraph ServerStream [Server Streaming]
        SSC[Client] -->|Single Request| SSS[Server]
        SSS -->|Stream of Responses| SSC
    end
    subgraph ClientStream [Client Streaming]
        CSC[Client] -->|Stream of Requests| CSS[Server]
        CSS -->|Single Response| CSC
    end
    subgraph Bidi [Bidirectional Streaming]
        BSC[Client] <-->|Stream both ways| BSS[Server]
    end

Teacher Mindset

Start with unary RPC for simple operations. Use server-streaming when the server produces multiple results over time. Use client-streaming when the client sends incremental data. Use bidirectional for real-time interaction.

Code Examples

// Example 1: All four RPC types defined
service ChatService {
  // Unary: simple send and receive
  rpc SendMessage (Message) returns (MessageAck);

  // Server streaming: subscribe to messages
  rpc SubscribeMessages (SubscribeRequest) returns (stream Message);

  // Client streaming: upload multiple messages
  rpc UploadMessages (stream Message) returns (UploadSummary);

  // Bidirectional streaming: real-time chat
  rpc ChatStream (stream ChatMessage) returns (stream ChatMessage);
}
// Example 2: Client implementation of all types
const client = new ChatService('localhost:50051', grpc.credentials.createInsecure());

// Unary
client.sendMessage({ text: 'Hello' }, (err, ack) => {});

// Server streaming
const sub = client.subscribeMessages({ room: 'general' });
sub.on('data', (msg) => console.log(msg.text));

// Client streaming
const upload = client.uploadMessages((err, summary) => {});
upload.write({ text: 'Batch 1' });
upload.write({ text: 'Batch 2' });
upload.end();

// Bidirectional streaming
const chat = client.chatStream();
chat.on('data', (msg) => console.log('Received:', msg.text));
chat.write({ text: 'Hello', room: 'general' });
// Example 3: Server implementation
const server = new grpc.Server();

server.addService(ChatService, {
  sendMessage: (call, callback) => {
    callback(null, { id: '1', timestamp: Date.now() });
  },
  subscribeMessages: (call) => {
    const room = call.request.room;
    const interval = setInterval(() => {
      call.write({ text: 'Live update', room });
    }, 1000);
    call.on('cancelled', () => clearInterval(interval));
  },
  uploadMessages: (call, callback) => {
    let count = 0;
    call.on('data', (msg) => { count++; });
    call.on('end', () => callback(null, { count }));
  },
  chatStream: (call) => {
    call.on('data', (msg) => {
      call.write({ text: `Echo: ${msg.text}`, room: msg.room });
    });
  }
});

Common Mistakes

  • Using unary RPC for operations that produce incremental results (use server-streaming)
  • Forgetting to handle backpressure in streaming RPCs
  • Using bidirectional streaming when a unary or server-streaming pattern suffices
  • Not implementing proper error handling for stream cancellations
  • Ignoring the memory implications of buffering large stream payloads

Practice

  1. Implement a unary RPC that returns a user profile.
  2. Implement a server-streaming RPC that returns a stream of product prices.
  3. Implement a client-streaming RPC that accepts a batch of log entries.
  4. Implement a bidirectional RPC for a simple echo server.
  5. Challenge: Benchmark unary vs server-streaming for returning 1000 records.

FAQ

When should I use server-streaming instead of unary?

Use server-streaming when the server produces multiple results over time (live feeds, progress updates, large datasets).

Can a client cancel a stream?

Yes. The client can cancel a stream by calling call.cancel(). The server receives a cancelled event.

How does gRPC handle backpressure in streams?

gRPC uses HTTP/2 flow control. When the receiver cannot keep up, the sender is automatically throttled.

What is the maximum message size in a stream?

The default is 4 MB. Configure with grpc.max_send_message_length and grpc.max_receive_message_length.

Can I mix stream types in a single service?

Yes. A service can have a mix of unary, server-stream, client-stream, and bidirectional methods.

Mini Project

Build a real-time stock ticker service with four RPC types: GetQuote (unary), SubscribeTicker (server-stream), ReportBatch (client-stream), and TraderChat (bidirectional).

What's Next

Next, you will learn about gRPC interceptors for cross-cutting concerns like logging and metrics.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro