Fanout Exchange
Copy to everyone
Section titled “Copy to everyone”A fanout exchange is the simplest router of all: it ignores the routing key completely and copies every message to every queue bound to it. One publish, N queues, N independent copies.
This is how you do broadcast — or, framed as a pattern, publish/subscribe. The producer announces an event once; every interested subscriber has its own queue bound to the exchange and receives its own copy to process independently.
A worked example: one event, three reactions
Section titled “A worked example: one event, three reactions”When an order is placed, three unrelated things must happen: send a confirmation email, write an audit record, and invalidate a cache. None of them should block the others, and adding a fourth reaction later shouldn’t touch the producer. A fanout exchange fits exactly.
flowchart LR p["publish 'order.placed'"] --> x["fanout exchange 'orders'"] x --> qe["queue: email"] x --> qa["queue: audit"] x --> qc["queue: cache-invalidation"]
Each consumer declares its own queue and binds it to the fanout exchange. Because each has a separate queue, one slow subscriber never holds up the others, and each processes at its own pace.
const ex = 'orders';await channel.assertExchange(ex, 'fanout', { durable: true });
// each subscriber: its own queue, bound with NO routing keyconst q = await channel.assertQueue('email', { durable: true });await channel.bindQueue(q.queue, ex, ''); // key ignored for fanout
// producer: publish once; routing key is irrelevantchannel.publish(ex, '', Buffer.from(JSON.stringify({ orderId: 123 })));ex = "orders"channel.exchange_declare(exchange=ex, exchange_type="fanout", durable=True)
# each subscriber: its own queue, bound with NO routing keychannel.queue_declare(queue="email", durable=True)channel.queue_bind(queue="email", exchange=ex) # key ignored for fanout
# producer: publish once; routing key is irrelevantchannel.basic_publish(exchange=ex, routing_key="", body='{"orderId": 123}')ex := "orders"ch.ExchangeDeclare(ex, "fanout", true, false, false, false, nil)
// each subscriber: its own queue, bound with NO routing keych.QueueDeclare("email", true, false, false, false, nil)ch.QueueBind("email", "", ex, false, nil) // key ignored for fanout
// producer: publish once; routing key is irrelevantch.PublishWithContext(ctx, ex, "", false, false, amqp.Publishing{Body: []byte(`{"orderId":123}`)})Fanout vs a work queue
Section titled “Fanout vs a work queue”Be careful not to confuse this with a work queue (next module). The difference is where the queue lives:
- Fanout / pub-sub: each subscriber has its own queue. Everyone gets every message. Use it to broadcast an event to independent handlers.
- Work queue: many workers share one queue. Each message goes to one worker. Use it to distribute tasks.
Same broker, opposite intent — and the deciding factor is simply “one queue shared, or a queue per consumer?”