The NATS Request-Reply pattern is fundamentally a way to ask a service for information and get an immediate answer, without you having to manage the "waiting" part yourself.

Let’s see it in action. Imagine a simple service that answers questions about the current time.

package main

import (
	"fmt"
	"log"
	"time"

	"github.com/nats-io/nats.go"
)

func main() {
	// Connect to NATS
	nc, err := nats.Connect(nats.DefaultURL)
	if err != nil {
		log.Fatalf("Failed to connect to NATS: %v", err)
	}
	defer nc.Close()

	log.Println("Connected to NATS")

	// Subscribe to the "time.request" subject
	sub, err := nc.Subscribe("time.request", func(msg *nats.Msg) {
		currentTime := time.Now().Format(time.RFC3339)
		err := nc.Publish(msg.Reply, []byte(currentTime))
		if err != nil {
			log.Printf("Error publishing reply: %v", err)
		}
		log.Printf("Replied to %s with %s", msg.Reply, currentTime)
	})
	if err != nil {
		log.Fatalf("Failed to subscribe: %v", err)
	}
	defer sub.Unsubscribe()

	log.Println("Listening for time requests on 'time.request'")

	// Keep the service running
	select {}
}

Now, from another Go program, we can make a request and wait for the reply:

package main

import (
	"fmt"
	"log"
	"time"

	"github.com/nats-io/nats.go"
)

func main() {
	// Connect to NATS
	nc, err := nats.Connect(nats.DefaultURL)
	if err != nil {
		log.Fatalf("Failed to connect to NATS: %v", err)
	}
	defer nc.Close()

	log.Println("Connected to NATS")

	subject := "time.request"
	requestData := []byte("What time is it?")

	// Send the request and wait for a reply (with a timeout)
	// The second argument is the timeout for the reply.
	msg, err := nc.Request(subject, requestData, 1*time.Second)
	if err != nil {
		log.Fatalf("Request failed: %v", err)
	}

	log.Printf("Received reply: %s", string(msg.Data))
}

When you run the first program (the server) and then the second (the client), you’ll see the client print the current time. The nc.Request call in the client is the magic. It publishes the requestData to the subject ("time.request"), and crucially, it automatically generates a unique temporary reply subject. It then subscribes to that temporary subject, waits for a message on it, and returns that message. If no message arrives within the timeout, it returns an error. The server, upon receiving a message on "time.request", sees the msg.Reply field and publishes its response directly to that specific subject.

This pattern elegantly solves the problem of needing immediate, synchronous-like interaction between distributed services without the complexity of traditional RPC frameworks. NATS handles the routing of the request to a listening service and the delivery of the reply back to the original requester. You don’t need to know which service will answer, only that a service listening on that subject will. The unique reply-to subject ensures the response gets back to the right place.

The nc.Request function is a convenience wrapper. Under the hood, it’s doing a Publish to the target subject and then setting up a temporary subscription to a unique subject generated by NATS. The publisher doesn’t need to know about this temporary subject; NATS ensures the reply finds its way back. This is why you can have multiple instances of your time server running, and NATS will deliver the request to one of them, and the reply will automatically be routed back to the client that made the original Request call.

What most people miss is how NATS manages the temporary reply subjects. When you call nc.Request, NATS doesn’t just send the message; it also implicitly creates a unique inbox subject (e.g., _INBOX.aBcDeF123). It then subscribes your client to this inbox subject before publishing the actual request. The server, receiving the request, sees the Reply field populated with this _INBOX.aBcDeF123 subject and publishes its response there. NATS then routes that response to the client that is subscribed to that specific inbox. This entire process is managed transparently by the NATS client library.

The next step is understanding how to handle situations where multiple services might respond to a request, and you only want the first one back, or how to implement more complex request-response flows with acknowledgments and error handling.

Want structured learning?

Take the full Nats course →