(using the amqp.node client)


This tutorial assumes RabbitMQ is installed and running on localhost on the standard port (5672). In case you use a different host, port or credentials, connections settings would require adjusting.

Where to get help

If you're having trouble going through this tutorial you can contact us through the mailing list or RabbitMQ community Slack.

In the previous tutorial we improved our logging system. Instead of using a fanout exchange only capable of dummy broadcasting, we used a direct one, and gained a possibility of selectively receiving the logs.

Although using the direct exchange improved our system, it still has limitations - it can't do routing based on multiple criteria.

In our logging system we might want to subscribe to not only logs based on severity, but also based on the source which emitted the log. You might know this concept from the syslog unix tool, which routes logs based on both severity (info/warn/crit...) and facility (auth/cron/kern...).

That would give us a lot of flexibility - we may want to listen to just critical errors coming from 'cron' but also all logs from 'kern'.

To implement that in our logging system we need to learn about a more complex topic exchange.

Topic exchange

Messages sent to a topic exchange can't have an arbitrary routing_key - it must be a list of words, delimited by dots. The words can be anything, but usually they specify some features connected to the message. A few valid routing key examples: "stock.usd.nyse", "nyse.vmw", "". There can be as many words in the routing key as you like, up to the limit of 255 bytes.

The binding key must also be in the same form. The logic behind the topic exchange is similar to a direct one - a message sent with a particular routing key will be delivered to all the queues that are bound with a matching binding key. However there are two important special cases for binding keys:

  • * (star) can substitute for exactly one word.
  • # (hash) can substitute for zero or more words.

It's easiest to explain this in an example:

Topic Exchange illustration, which is all explained in the following text.
digraph { bgcolor=transparent; truecolor=true; rankdir=LR; node [style="filled"]; // P [label="P", fillcolor="#00ffff"]; subgraph cluster_X1 { label="type=topic"; color=transparent; X [label="X", fillcolor="#3333CC"]; }; subgraph cluster_Q1 { label="Q1"; color=transparent; Q1 [label="{||||}", fillcolor="red", shape="record"]; }; subgraph cluster_Q2 { label="Q2"; color=transparent; Q2 [label="{||||}", fillcolor="red", shape="record"]; }; C1 [label=<C<font point-size="7">1</font>>, fillcolor="#33ccff"]; C2 [label=<C<font point-size="7">2</font>>, fillcolor="#33ccff"]; // P -> X; X -> Q1 [label="*.orange.*"]; X -> Q2 [label="*.*.rabbit"]; X -> Q2 [label="lazy.#"]; Q1 -> C1; Q2 -> C2; }

In this example, we're going to send messages which all describe animals. The messages will be sent with a routing key that consists of three words (two dots). The first word in the routing key will describe speed, second a colour and third a species: "<speed>.<colour>.<species>".

We created three bindings: Q1 is bound with binding key "*.orange.*" and Q2 with "*.*.rabbit" and "lazy.#".

These bindings can be summarised as:

  • Q1 is interested in all the orange animals.
  • Q2 wants to hear everything about rabbits, and everything about lazy animals.

A message with a routing key set to "" will be delivered to both queues. Message "" also will go to both of them. On the other hand "" will only go to the first queue, and "" only to the second. "" will be delivered to the second queue only once, even though it matches two bindings. "" doesn't match any binding so it will be discarded.

What happens if we break our contract and send a message with one or four words, like "orange" or ""? Well, these messages won't match any bindings and will be lost.

On the other hand "", even though it has four words, will match the last binding and will be delivered to the second queue.

Topic exchange

Topic exchange is powerful and can behave like other exchanges.

When a queue is bound with "#" (hash) binding key - it will receive all the messages, regardless of the routing key - like in fanout exchange.

When special characters "*" (star) and "#" (hash) aren't used in bindings, the topic exchange will behave just like a direct one.

Putting it all together

We're going to use a topic exchange in our logging system. We'll start off with a working assumption that the routing keys of logs will have two words: "<facility>.<severity>".

The code is almost the same as in the previous tutorial.

The code for emit_log_topic.js:

#!/usr/bin/env node

var amqp = require('amqplib/callback_api');

amqp.connect('amqp://localhost', function(error0, connection) {
  if (error0) {
    throw error0;
  connection.createChannel(function(error1, channel) {
    if (error1) {
      throw error1;
    var exchange = 'topic_logs';
    var args = process.argv.slice(2);
    var key = (args.length > 0) ? args[0] : '';
    var msg = args.slice(1).join(' ') || 'Hello World!';

    channel.assertExchange(exchange, 'topic', {
      durable: false
    channel.publish(exchange, key, Buffer.from(msg));
    console.log(" [x] Sent %s:'%s'", key, msg);

  setTimeout(function() {
  }, 500);

The code for receive_logs_topic.js:

#!/usr/bin/env node

var amqp = require('amqplib/callback_api');

var args = process.argv.slice(2);

if (args.length == 0) {
  console.log("Usage: receive_logs_topic.js <facility>.<severity>");

amqp.connect('amqp://localhost', function(error0, connection) {
  if (error0) {
    throw error0;
  connection.createChannel(function(error1, channel) {
    if (error1) {
      throw error1;
    var exchange = 'topic_logs';

    channel.assertExchange(exchange, 'topic', {
      durable: false

    channel.assertQueue('', {
      exclusive: true
    }, function(error2, q) {
      if (error2) {
        throw error2;
      console.log(' [*] Waiting for logs. To exit press CTRL+C');

      args.forEach(function(key) {
        channel.bindQueue(q.queue, exchange, key);

      channel.consume(q.queue, function(msg) {
        console.log(" [x] %s:'%s'", msg.fields.routingKey, msg.content.toString());
      }, {
        noAck: true

To receive all the logs:

./receive_logs_topic.js "#"

To receive all logs from the facility "kern":

./receive_logs_topic.js "kern.*"

Or if you want to hear only about "critical" logs:

./receive_logs_topic.js "*.critical"

You can create multiple bindings:

./receive_logs_topic.js "kern.*" "*.critical"

And to emit a log with a routing key "kern.critical" type:

./emit_log_topic.js "kern.critical" "A critical kernel error"

Have fun playing with these programs. Note that the code doesn't make any assumption about the routing or binding keys, you may want to play with more than two routing key parameters.

(Full source code for emit_log_topic.js and receive_logs_topic.js)

Next, find out how to do a round trip message as a remote procedure call in tutorial 6

Production [Non-]Suitability Disclaimer

Please keep in mind that this and other tutorials are, well, tutorials. They demonstrate one new concept at a time and may intentionally oversimplify some things and leave out others. For example topics such as connection management, error handling, connection recovery, concurrency and metric collection are largely omitted for the sake of brevity. Such simplified code should not be considered production ready.

Please take a look at the rest of the documentation before going live with your app. We particularly recommend the following guides: Publisher Confirms and Consumer Acknowledgements, Production Checklist and Monitoring.

Getting Help and Providing Feedback

If you have questions about the contents of this tutorial or any other topic related to RabbitMQ, don't hesitate to ask them on the RabbitMQ mailing list.

Help Us Improve the Docs <3

If you'd like to contribute an improvement to the site, its source is available on GitHub. Simply fork the repository and submit a pull request. Thank you!

1 "Hello World!"

The simplest thing that does something

(P) -> [|||] -> (C)

2 Work queues

Distributing tasks among workers (the competing consumers pattern)

Producer -> Queue -> Consuming: Work Queue used to distribute time-consuming tasks among multiple workers.

3 Publish/Subscribe

Sending messages to many consumers at once

Producer -> Queue -> Consuming: deliver a message to multiple consumers.

4 Routing

Receiving messages selectively

Producer -> Queue -> Consuming: subscribe to a subset of the messages only.

5 Topics

Receiving messages based on a pattern (topics)

Producer -> Queue -> Consuming: receiving messages based on a pattern (topics).


Request/reply pattern example

Producer -> Queue -> Consuming: RPC (Remote Procedure Call), the request/reply pattern.

7 Publisher Confirms

Reliable publishing with publisher confirms