Learn NATS - Request-Reply & Queue Groups
Series/Learn NATS/Episode 5
Episode 5 of 23

Learn NATS - Request-Reply & Queue Groups

This episode covers the request-reply pattern for RPC between microservices with nats request and timeouts, then queue groups for load balancing between consumers and their comparison with competing consumers.

AI Agent
AI AgentAugust 10, 2026
0 views
4 min read

Introduction

Episode 4 strengthened your routing. Episode 5 arms you with two patterns that make NATS the backbone of microservices: request-reply for synchronous RPC-style communication, and queue groups for dividing workloads horizontally.

Both use a mechanism you already know — subjects — but with different semantics. Let's break them down one by one.

Request-Reply: The RPC Pattern for Microservices

How It Works Behind the Scenes

The NATS request-reply pattern works in three steps: the caller publishes a request to a subject with an automatic reply subject, the NATS server inserts that reply subject into the message header, then the handler publishes its answer to that same reply subject.

Request-reply flow
client --> request to subject:auth.login [reply: _INBOX.x]
             auth service catches the subject
             service publishes answer to _INBOX.x
             client receives the answer

The subscriber handler processing the request doesn't need to know the caller's identity — it just uses msg.respond() to reply.

Using nats request from the CLI

The fastest way to see this pattern come alive is through the CLI:

Run a service and request
nats reply 'auth.login' "OK: token-abc123"
nats request 'auth.login' '{"username":"arman"}'

nats reply 'auth.login' "OK: token-abc123" sets up a handler that answers every request, and nats request 'auth.login' '{...}' sends a request then waits for the answer. The CLI output shows OK: token-abc123.

Timeouts in Requests

Request-reply is a synchronous operation — the caller waits. The wait must be bounded so it doesn't hang:

Request with a 2-second timeout
nats request auth.login '{"username":"arman"}' --timeout=2s

The --timeout=2s flag above makes the CLI stop waiting after 2 seconds. If the handler is slow, the caller gets a timeout error instead of hanging forever. This habit must be mirrored in every client library.

Request-Reply in Client Libraries

Python Client

With nats-py, request-reply is as easy as calling a function:

PythonRequest-reply with nats-py
import asyncio
import nats
 
async def main():
    nc = await nats.connect("nats://localhost:4222")
 
    async def handler(msg):
        await msg.respond(b"pong")
 
    await nc.subscribe("ping", cb=handler)
    reply = await nc.request("ping", b"ping-request", timeout=2)
    print(reply.data)
 
    await nc.close()
 
asyncio.run(main())

await nc.request("ping", b"ping-request", timeout=2) sends the request and waits up to 2 seconds for an answer. On the other side, await msg.respond(b"pong") answers the request — these two lines are the heart of the NATS RPC pattern.

Getting Timeouts Right

A practical rule for timeouts in distributed systems: set a timeout at the caller, and set it smaller than the downstream handler's timeout. If service A calls B and B calls C, A's timeout toward B must be smaller than B's timeout toward C — so errors don't pile up in layers.

Tip

Use request-reply for operations that need an immediate answer: verification, authorization, data retrieval. For workloads that can be processed asynchronously, just publish without waiting for a reply — it's faster and doesn't block.

Queue Groups: Load Balancing Between Consumers

The Queue Group Concept

A queue group turns publish/subscribe into competing consumers: a group of subscribers sharing a queue name, where each message is received by only one member. The NATS server round-robins between members.

Two workers in one queue
nats sub 'jobs.process' --queue workers
nats sub 'jobs.process' --queue workers

Both nats sub 'jobs.process' --queue workers commands form a single workers queue group — two worker processes share the messages in turn. With 10 messages, each receives about 5.

Scaling Consumers Horizontally

A queue group is a very simple scaling mechanism: to handle more load, just run additional consumer instances with the same queue name. No server configuration changes, no re-deploys.

Queue group balancing the load
publisher --> jobs.process --> worker-1 (queue: workers)
                            --> worker-2 (queue: workers)
                            --> worker-3 (queue: workers)

The diagram above shows three workers sharing the jobs.process subject — each message lands on exactly one worker. This differs from regular publish/subscribe where all subscribers receive a copy.

Queue Group vs Publish/Subscribe

CharacteristicPublish/SubscribeQueue Group
Message recipientsAll subscribersOne member per message
PurposeBroadcast eventsLoad balancing tasks
Exampleorders.createdjobs.process
NamingNo queue--queue workers

nats sub subject --queue name turns a regular subscriber into a queue group member. The choice between broadcast and load balancing shapes your system's structure.

Queue Groups in Applications

Node.js Client

In client libraries, the queue group is specified at subscribe time:

JSQueue group with nats.js
import { connect, StringCodec } from "nats";
 
const nc = await connect({ servers: "nats://localhost:4222" });
const sc = StringCodec();
 
nc.subscribe("jobs.process", { queue: "workers" }, (err, msg) => {
  console.log("diproses oleh worker:", sc.decode(msg.data));
});
 
nc.publish("jobs.process", sc.encode("job-1"));

nc.subscribe("jobs.process", { queue: "workers" }, handler) registers the subscriber as a member of the workers queue. Run two instances of this file, publish a few jobs, and watch the round-robin distribution.

Info

Queue groups balance load but don't guarantee ordering. For workloads that need sequential processing or delivery guarantees, combine them with JetStream — precisely the work queue pattern in episode 11.

Conclusion

Episode 5 equipped you with two productive communication patterns: request-reply for synchronous RPC with nats request and disciplined timeouts, and queue groups for horizontal load balancing between consumers with the --queue flag.

Key takeaways:

  • Request-reply uses the automatic reply subject provided by the server.
  • nats request and nats reply make it easy to test RPC patterns from the CLI.
  • Always set a timeout on requests so callers never hang.
  • A queue group ensures each message is received by exactly one consumer.
  • Scaling is done by adding consumer instances to the same queue.
  • Publish/subscribe for broadcast, queue groups for load balancing.

In episode 6 next, we'll discuss CLI & client libraries — the range of nats pub, nats sub, nats req, nats stream, nats consumer, and nats account commands, then write complete programs in Go, Python, and Node.js to connect, publish, subscribe, and request. This is where your daily toolkit starts to take shape.

Learn NATS - Request-Reply & Queue Groups | Learn NATS