Course contentsShow
← Node.js
Lesson 1237 of 1,98011. Task Queues and Background ProcessingFree lesson

Implementing Request-Response over Message Queues

Use temporary return queues to perform synchronous-like calls over an asynchronous message transport.

In this lesson, you will learn how to turn an asynchronous, fire-and-forget message queue into a synchronous request-response pattern using temporary "reply queues."

The Challenge of Asynchronicity

By design, message queues are asynchronous: you push a task and move on. However, some workflows require immediate feedback—for example, asking a worker to perform a complex calculation and waiting for the result before proceeding. To achieve this, we can implement the Request-Response pattern using correlation IDs and temporary queues.

The Temporary Return Queue Pattern

The concept is simple: when the client sends a request, it creates a unique, short-lived queue specifically for the answer. The request message includes two metadata headers:

  1. replyTo: The name of the temporary queue the worker should send the answer to.
  2. correlationId: A unique ID generated by the client to map the eventual response back to the original request.

When the worker finishes, it pushes the result to the queue specified in the replyTo header, tagging the message with the correlationId so the client can pair the response with the pending request.

Node.js Implementation Concept

Imagine a client requesting a computation:

// Client side
const replyQueue = await channel.assertQueue('', { exclusive: true });
const correlationId = generateUUID();

channel.sendToQueue('task_queue', Buffer.from(payload), {
  replyTo: replyQueue.queue,
  correlationId
});

// Listener waiting for the specific response
channel.consume(replyQueue.queue, (msg) => {
  if (msg.properties.correlationId === correlationId) {
    console.log('Got response:', msg.content.toString());
  }
});

The worker simply reads the replyTo property from the incoming message:

// Worker side
channel.consume('task_queue', (msg) => {
  const result = performWork(msg.content);
  
  channel.sendToQueue(msg.properties.replyTo, Buffer.from(result), {
    correlationId: msg.properties.correlationId
  });
  channel.ack(msg);
});

By using exclusive: true when asserting the replyQueue, Node.js ensures the queue is deleted as soon as the client connection closes, preventing clutter in your message broker. This allows you to treat your background workers as if they were performing synchronous RPC calls, despite the underlying infrastructure being fully asynchronous.

Key Takeaway: By passing a unique reply-to address and a correlation ID within your message headers, you can transform asynchronous message passing into a reliable request-response interaction.