NATS Logo by Example

Request-Reply in Messaging

The request-reply pattern allows a client to send a message and expect a reply of some kind. In practice, the request message will either be a command, which is an intention for service to carry out some work that results in a state change, or a query, which is a request for information.

Unlike request-reply constrained protocols like HTTP, NATS is not limited to a strict point-to-point interaction between a client and server. The request-reply pattern is built on top of the core publish-subscribe model.

By default, this means that any one of subscribers could be a responder and reply to the client. However, because NATS is not limited to point-to-point interactions, the client could indicate to NATS that multiple replies should be allowed.

This example shows the basics of the request-reply pattern including the standard “no responders” error if there are no subscribers available to handle and reply to the requesting message.

CLI Go Python JavaScript Rust C# C#2 Java Ruby Elixir Crystal C
Jump to the output or the recording
$ nbe run messaging/request-reply/deno
View the source code or learn how to run this example yourself

Code

import the library - in node.js import {connect, etc} from "nats"; or if not doing a module, const {connect, etc} = require("nats");

import { connect, Empty } from "https://deno.land/x/nats@v1.16.0/src/mod.ts";




const servers = Deno.env.get("NATS_URL") || "nats://localhost:4222";

Create a client connection to an available NATS server.

const nc = await connect({
  servers: servers.split(","),
});

In addition to vanilla publish-request, NATS supports request-reply interactions as well. Under the covers, this is just an optimized pair of publish-subscribe operations. The request handler is just a subscription that responds to a message sent to it. This kind of subscription is called a service.

const sub = nc.subscribe("greet.*", {
  callback: (err, msg) => {
    if (err) {
      console.log("subscription error", err.message);
      return;
    }

Parse out the second token in the subject (everything after greet.) and use it as part of the response message.

    const name = msg.subject.substring(6);
    msg.respond(`hello, ${name}`);
  },
});

Now we can use the built-in request method to do the service request. A payload is optional, and we skip setting it right now. In addition, you can specify an optional timeout, but we’ll use the default for now.

let rep = await nc.request("greet.joe");
console.log(rep.string());

here put a payload

rep = await nc.request("greet.sue", "hello!");
console.log(rep.string());

and here we set a timeout (with an empty payload), and a timeout of 3 seconds - specified as milliseconds

rep = await nc.request("greet.bob", Empty, {timeout: 3000});
console.log(rep.string());

What happens if the service is unavailable? We can simulate this by unsubscribing our handler from above. Now if we make a request, we will expect an error.

sub.unsubscribe();


nc.request("greet.joe")
  .catch((err) => {
    console.log(
      "request failed with: ",
      err.code === "503" ? "timeout" : err.message,
    );
  });



Close the connection

await nc.drain();

Output

[?25l[+] Building 0.0s (0/0)                                                                                                                                                                                                             
[?25h[?25l[+] Building 0.0s (0/0)                                                                                                                                                                                                             
[?25hhello, joe
hello, sue
hello, bob
request failed with:  timeout

Recording

Note, playback is half speed to make it a bit easier to follow.