Skip to content

Publish/Subscribe

Sometimes a single event should trigger several unrelated things. An order is placed, and independently: the email service sends a confirmation, the analytics service records it, and the cache service invalidates a page. None of them knows about the others, and each must get its own copy of the event.

That is publish/subscribe, and the mechanism is a fanout exchange (or a topic exchange when you also want filtering) with one queue per subscriber.

flowchart LR
  p["Producer
(order placed)"] --> x["fanout exchange"]
  x --> qe["queue: email"] --> se["email service"]
  x --> qa["queue: analytics"] --> sa["analytics service"]
  x --> qc["queue: cache"] --> sc["cache service"]
A fanout exchange copies each event to every subscriber's queue

This is the distinction the whole module hinges on, so make it concrete:

Work queuePublish/subscribe
QueuesOne shared queueOne queue per subscriber
Each message goes toExactly one workerEvery subscriber (a copy each)
Add a consumer to…Increase throughputAdd an independent reaction
ExchangeOften the default/directFanout (or topic)

If three “subscribers” all bound the same queue, you’d have a work queue by accident — the message would go to only one of them. Pub/sub requires each subscriber to declare and bind its own queue.

A common, clean setup: each subscriber declares an exclusive, auto-delete queue (unique to that consumer instance) and binds it to the shared fanout exchange. The exchange copies every message into every bound queue.

const channel = await conn.createChannel();
await channel.assertExchange('orders', 'fanout', { durable: true });
// Each subscriber gets its own queue.
const { queue } = await channel.assertQueue('', { exclusive: true });
await channel.bindQueue(queue, 'orders', '');
channel.consume(queue, (msg) => {
if (!msg) return;
handleOrderEvent(msg.content.toString());
channel.ack(msg);
});

The producer never changes as you add subscribers — it just publishes to the orders exchange. Want a new fraud-detection reaction? Start a service that binds its own queue. That is the decoupling payoff from the foundations module, made concrete.

Exclusive auto-delete queues vanish when the subscriber disconnects — good for a live dashboard that only cares about events while it’s watching. For a subscriber that must not miss events while it’s briefly down (like the email service), give it a named, durable queue instead, so messages accumulate for it and wait until it reconnects.

In publish/subscribe, how many copies of an event do three subscribers receive?
What mistake turns pub/sub into an accidental work queue?
Which exchange type is the natural fit for pure broadcast?
A subscriber must not miss events while it briefly restarts. What should it use?