DEV Community

Cover image for Understanding RabbitMQ Exchange Types: Direct, Fanout, and Topic
Alex Beygi
Alex Beygi

Posted on

Understanding RabbitMQ Exchange Types: Direct, Fanout, and Topic

RabbitMQ is a message broker that implements the AMQP (Advanced Message Queuing Protocol). At the heart of RabbitMQ’s routing model lies the exchange. Exchanges are responsible for receiving messages from producers and routing them to one or more queues based on defined rules.

Choosing the correct exchange type is critical for building scalable, correct, and maintainable systems—especially in real-time systems such as chat, notifications, and event-driven architectures.

This article explains the three most commonly used exchange types—Direct, Fanout, and Topic—and demonstrates how each routes messages, when to use them, and common mistakes to avoid.

Core Concepts Recap

Before diving into exchange types, it’s important to understand a few fundamental components:

Producer: Sends messages to an exchange.
Exchange: Routes messages to queues based on rules.
Queue: Stores messages until they are consumed.
Binding: A rule that links a queue to an exchange.
Routing Key: A string attached to a message, used by the exchange to decide where the message goes.

An exchange never stores messages. It only decides where messages should be routed.

1. Direct Exchange

How Direct Exchange Works
A direct exchange routes messages to queues only if the routing key exactly matches the binding key.

  • Routing is deterministic
  • Matching is strict equality
  • No pattern matching is performed

Let's take at a PHP/Laravel example:

Install the client:

composer require php-amqplib/php-amqplib

Example .env

Q_HOST=127.0.0.1
MQ_PORT=5672
MQ_USER=guest
MQ_PASS=guest
MQ_VHOST=/
Enter fullscreen mode Exit fullscreen mode

Typical connection factory (recommended to reuse in services):

use PhpAmqpLib\Connection\AMQPStreamConnection;
 embed function mqConnection(): AMQPStreamConnection {
    return new AMQPStreamConnection(
        env('MQ_HOST'),
        env('MQ_PORT'),
        env('MQ_USER'),
        env('MQ_PASS'),
        env('MQ_VHOST')
    ); 
}
Enter fullscreen mode Exit fullscreen mode

Publish to a Direct Exchange

use PhpAmqpLib\Message\AMQPMessage;

public function publishOrderCreated(array $payload)
{
    $connection = mqConnection();
    $channel = $connection->channel();

    $exchange = 'orders.direct';
    $queue    = 'orders.created.queue';
    $key      = 'order.created';

    // Topology
    $channel->exchange_declare($exchange, 'direct', false, true, false);
    $channel->queue_declare($queue, false, true, false, false);
    $channel->queue_bind($queue, $exchange, $key);

    $msg = new AMQPMessage(json_encode($payload), [
        'content_type'  => 'application/json',
        'delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT,
    ]);

    $channel->basic_publish($msg, $exchange, $key);

    $channel->close();
    $connection->close();
}
Enter fullscreen mode Exit fullscreen mode

Consumer for a Direct Queue (manual ack)

$channel->basic_qos(null, 1, null);

$channel->basic_consume('orders.created.queue', '', false, false, false, false,
    function ($msg) {
        $data = json_decode($msg->body, true);

        try {
            // process...
            $msg->ack();
        } catch (\Throwable $e) {
            $msg->nack(false, true); // requeue on failure
        }
    }
);

while ($channel->is_consuming()) {
    $channel->wait();
}
Enter fullscreen mode Exit fullscreen mode

When to Use Direct Exchange

Task queues (background jobs)

Worker pools

  • Point-to-point messaging
  • Routing messages to a specific consumer group

Characteristics

✔ Predictable routing
✔ High performance
❌ Not suitable for broadcast
❌ No pattern matching

2. Fanout Exchange

How Fanout Exchange Works

A fanout exchange routes messages to all bound queues, ignoring the routing key entirely.

  • Routing key is irrelevant
  • Every bound queue receives a copy
  • Pure broadcast semantics

Visual Explanation

In the diagram’s Fanout Exchange section:

  • The producer sends a message
  • The fanout exchange delivers the message to Queue X, Queue Y, and Queue Z
  • All consumers receive the same message independently

When to Use (Chat Example)

Fanout is perfect when multiple servers must receive the same message, such as a chat system where multiple WebSocket nodes need to forward messages to connected clients.

Example:

  • ws.node1.queue
  • ws.node2.queue
  • ws.node3.queue

Every node receives the message and delivers it to clients connected to that node.

Laravel Example: Publish to Fanout (Chat Broadcast)

use PhpAmqpLib\Message\AMQPMessage;

public function broadcastChatMessage(array $payload)
{
    $connection = mqConnection();
    $channel = $connection->channel();

    $exchange = 'chat.fanout';

    $channel->exchange_declare($exchange, 'fanout', false, true, false);

    $msg = new AMQPMessage(json_encode($payload), [
        'content_type' => 'application/json',
    ]);

    // routing key is ignored for fanout; pass '' conventionally
    $channel->basic_publish($msg, $exchange, '');

    $channel->close();
    $connection->close();
}
Enter fullscreen mode Exit fullscreen mode

Fanout Consumers: Each server has its own queue

On WebSocket Server 1:

$exchange = 'chat.fanout';
$queue = 'ws.server1.queue';

$channel->exchange_declare($exchange, 'fanout', false, true, false);
$channel->queue_declare($queue, false, true, false, false);
$channel->queue_bind($queue, $exchange);

$channel->basic_consume($queue, '', false, true, false, false, function($msg) {
    // push to local websocket clients
    echo "server1 received: {$msg->body}\n";
});
Enter fullscreen mode Exit fullscreen mode

On WebSocket Server 2: same pattern but ws.server2.queue.

Result: all websocket servers receive each message and forward to their connected users.

3. Topic Exchange

How Topic Exchange Works

A topic exchange routes messages using pattern matching on routing keys.

Topic exchanges route messages based on patterns in routing keys (dot-separated words):

Wildcards:

* matches exactly one word
# matches zero or more words

Example routing key:

embed logs.error.app1

Example bindings:

  • logs.error.* ✅
  • logs.*.app1 ✅
  • logs.# ✅

Topic is best for:

  • event buses in microservices
  • routing by domain + action + sub-type
  • logging/event pipelines

Laravel Example: Topic Exchange for Events

use PhpAmqpLib\Message\AMQPMessage;

public function publishEvent(string $routingKey, array $payload)
{
    $connection = mqConnection();
    $channel = $connection->channel();

    $exchange = 'events.topic';

    $channel->exchange_declare($exchange, 'topic', false, true, false);

    $msg = new AMQPMessage(json_encode($payload), [
        'content_type' => 'application/json',
    ]);

    $channel->basic_publish($msg, $exchange, $routingKey);

    $channel->close();
    $connection->close();
} 
Enter fullscreen mode Exit fullscreen mode

Bind Consumers by Pattern

All logs for app1:

$queue = 'logs.app1.all.queue';
$channel->queue_declare($queue, false, true, false, false);
$channel->queue_bind($queue, 'events.topic', 'logs.#.app1');
Enter fullscreen mode Exit fullscreen mode

Everything:

$queue = 'logs.all.queue';
$channel->queue_bind($queue, 'events.topic', '#');
Enter fullscreen mode Exit fullscreen mode

Direct vs Fanout vs Topic: Choosing Correctly

Requirement Best Exchange
One specific queue processes each message Direct
Broadcast to every consumer Fanout
Flexible routing with patterns Topic
Chat broadcast to many WebSocket nodes Fanout
Event bus across services Topic
Worker queues (jobs) Direct

Common Production Notes (Laravel):

1) Don’t auto-ack unless losing messages is acceptable

In your earlier consumer, you used no_ack=true. That makes delivery at-most-once (messages can be lost on crash). For real systems, prefer manual ack.

2) Use durable topology for reliability

Use:

  • durable=true for exchanges/queues
  • delivery_mode=PERSISTENT for messages

3) Prefer long-running workers (Artisan + Supervisor)

Consumers should be CLI workers (Supervisor/systemd/Docker), not HTTP requests.

Conclusion

Direct: exact routing, ideal for job workers.
Fanout: pure broadcast, perfect for chat/WebSocket clusters.
Topic: pattern routing, best for event buses and log pipelines.

Top comments (0)