The orders service publishes events, and there are already several receivers: emails, the warehouse, analytics. Each has its own interests: emails need only payments, the warehouse works with one region, analytics wants everything. Who decides which message goes into which queue? That is the job of the exchange. In this lesson we study its types, direct, fanout, topic and headers, learn to build subscriptions, and then look at what to do when processing a message fails: RabbitMQ has dead-letter queues, TTL and retries with a delay for that.
How an exchange decides
Recall the chain: producer → exchange → binding → queue → consumer. An exchange stores nothing: it looks at the message's routing key (routing key) and at the bindings (binding) and copies the message into suitable queues. A binding is a pair "queue + binding key" (binding key). The exchange type defines how these two keys are compared:
| Type | Rule | Example |
|---|---|---|
direct | the keys match exactly | log level: error |
fanout | the key is ignored, everybody gets a copy | an event for all services |
topic | the key is matched against a pattern | order.*.paid |
headers | headers are compared, not the key | format=pdf |
direct: an exact match
Example: logs by level. Errors (error) go to the alerts queue, and info and warning go to the console:
import amqp from 'amqplib';
const connection = await amqp.connect('amqp://localhost');
const channel = await connection.createChannel();
await channel.assertExchange('logs', 'direct', { durable: true });
await channel.assertQueue('alerts', { durable: true });
await channel.assertQueue('console', { durable: true });
await channel.bindQueue('alerts', 'logs', 'error');
await channel.bindQueue('console', 'logs', 'info');
await channel.bindQueue('console', 'logs', 'warning');
const send = (level, text) =>
channel.publish('logs', level, Buffer.from(text));
send('error', 'Payment service is down');
send('info', 'Order 7 created');
send('trace', 'nobody listens to this level');A message with the key error goes to alerts, and info goes to console. But trace, which has no binding, silently disappears: the exchange does not know where to send it and drops it. To notice such losses, publish has the mandatory flag and an exchange has the alternate exchange setting.
💡 If you bind several queues to one key, each of them gets its own copy:
directcan do that too.
fanout: a copy for everyone
fanout ignores the key and copies every message into all bound queues. This is the simplest pub/sub from the first lesson of the module. A subscriber declares its own queue and binds it to the exchange: