RabbitMQ with Node.js

Node.js applications connect to RabbitMQ using the amqplib library, which provides a full AMQP 0-9-1 implementation for both callback-based and promise/async-await styles. Its lightweight design and non-blocking I/O make it a natural fit for Node.js event-driven applications.

Installing amqplib

npm install amqplib

Connecting to RabbitMQ

const amqp = require('amqplib');

async function connect() {
  const connection = await amqp.connect('amqp://localhost');
  const channel = await connection.createChannel();
  return { connection, channel };
}

For custom credentials and vhost:

const connection = await amqp.connect({
  protocol: 'amqp',
  hostname: 'rabbitmq.example.com',
  port: 5672,
  username: 'myuser',
  password: 'mypassword',
  vhost: '/production'
});

Publishing a Message

const amqp = require('amqplib');

async function publishOrder(order) {
  const connection = await amqp.connect('amqp://localhost');
  const channel = await connection.createChannel();

  const queue = 'orders';
  await channel.assertQueue(queue, { durable: true });

  const message = JSON.stringify(order);
  channel.sendToQueue(queue, Buffer.from(message), {
    persistent: true,          // delivery_mode = 2
    contentType: 'application/json'
  });

  console.log(`Order published: ${message}`);
  await channel.close();
  await connection.close();
}

publishOrder({ orderId: 1001, product: 'Keyboard', qty: 2 });

Consuming Messages

const amqp = require('amqplib');

async function startConsumer() {
  const connection = await amqp.connect('amqp://localhost');
  const channel = await connection.createChannel();

  const queue = 'orders';
  await channel.assertQueue(queue, { durable: true });
  await channel.prefetch(1);  // fair dispatch

  console.log('Waiting for orders...');

  channel.consume(queue, async (msg) => {
    if (msg === null) return;  // consumer was cancelled

    const order = JSON.parse(msg.content.toString());
    console.log(`Processing order ${order.orderId}`);

    try {
      await processOrder(order);
      channel.ack(msg);             // success
    } catch (err) {
      console.error(`Failed: ${err.message}`);
      channel.nack(msg, false, true); // requeue on error
    }
  });
}

startConsumer().catch(console.error);

Working with Exchanges

async function setupTopicExchange() {
  const conn = await amqp.connect('amqp://localhost');
  const ch = await conn.createChannel();

  // Declare a topic exchange
  await ch.assertExchange('app-events', 'topic', { durable: true });

  // Declare a queue and bind with a pattern
  const q = await ch.assertQueue('order-events', { durable: true });
  await ch.bindQueue(q.queue, 'app-events', 'order.#');

  // Publish to the exchange
  ch.publish(
    'app-events',
    'order.placed',
    Buffer.from(JSON.stringify({ orderId: 2001 })),
    { persistent: true, contentType: 'application/json' }
  );

  console.log('Event published to topic exchange');
}

Publisher Confirms

async function publishWithConfirm(message) {
  const conn = await amqp.connect('amqp://localhost');
  const ch = await conn.createConfirmChannel();  // confirm channel

  await ch.assertQueue('critical-tasks', { durable: true });

  return new Promise((resolve, reject) => {
    ch.sendToQueue(
      'critical-tasks',
      Buffer.from(message),
      { persistent: true },
      (err, ok) => {
        if (err) {
          reject(new Error(`Message nacked: ${err.message}`));
        } else {
          resolve('Message confirmed by broker');
        }
      }
    );
  });
}

publishWithConfirm('{"task":"send-invoice"}')
  .then(console.log)
  .catch(console.error);

Fanout Exchange for Pub/Sub

// Publisher
async function publishNews(newsItem) {
  const conn = await amqp.connect('amqp://localhost');
  const ch = await conn.createChannel();
  await ch.assertExchange('news', 'fanout', { durable: false });
  ch.publish('news', '', Buffer.from(JSON.stringify(newsItem)));
  await ch.close();
  await conn.close();
}

// Subscriber
async function subscribeToNews() {
  const conn = await amqp.connect('amqp://localhost');
  const ch = await conn.createChannel();
  await ch.assertExchange('news', 'fanout', { durable: false });

  // Exclusive, auto-delete temporary queue
  const q = await ch.assertQueue('', { exclusive: true });
  await ch.bindQueue(q.queue, 'news', '');

  ch.consume(q.queue, (msg) => {
    if (msg) {
      console.log(`News received: ${msg.content.toString()}`);
      ch.ack(msg);
    }
  });
}

Error Handling and Connection Recovery

amqplib does not include automatic reconnection. Implement it manually:

async function connectWithRetry(url, retries = 5, delay = 5000) {
  for (let i = 0; i < retries; i++) {
    try {
      const conn = await amqp.connect(url);
      console.log('Connected to RabbitMQ');

      conn.on('error', (err) => {
        console.error('Connection error:', err.message);
      });
      conn.on('close', () => {
        console.warn('Connection closed, reconnecting...');
        setTimeout(() => connectWithRetry(url, retries, delay), delay);
      });

      return conn;
    } catch (err) {
      console.warn(`Connection attempt ${i + 1} failed. Retrying in ${delay}ms...`);
      await new Promise(r => setTimeout(r, delay));
    }
  }
  throw new Error('Could not connect to RabbitMQ after maximum retries');
}

Using amqp-connection-manager (Recommended for Production)

The amqp-connection-manager package wraps amqplib with automatic reconnection and channel rebuilding:

npm install amqp-connection-manager
const amqpConnectionManager = require('amqp-connection-manager');

const connection = amqpConnectionManager.connect(['amqp://localhost']);

const channelWrapper = connection.createChannel({
  setup: (channel) => Promise.all([
    channel.assertQueue('orders', { durable: true }),
    channel.prefetch(1)
  ])
});

// Publish (queued internally if connection is down, sent when reconnected)
channelWrapper.sendToQueue('orders',
  Buffer.from(JSON.stringify({ orderId: 3001 })),
  { persistent: true }
);

Summary

Node.js connects to RabbitMQ using amqplib with async/await. Use createChannel() for standard operations and createConfirmChannel() for publisher confirms. Declare queues with assertQueue, exchanges with assertExchange, and bindings with bindQueue. Always call prefetch(1) for fair dispatch and ack() / nack() for manual acknowledgements. For production, use amqp-connection-manager to handle reconnection automatically so your service recovers from broker restarts without manual intervention.

Leave a Comment

Your email address will not be published. Required fields are marked *