← Course Index

RabbitMQ — Async Jobs & Message Queues

~30 min · RabbitMQ

Ref
Primary Source
RabbitMQ Official Tutorials — Node.js

Step-by-step tutorials from Hello World to topic exchanges. The single best RabbitMQ learning resource. Read →

Why RabbitMQ?

When a user signs up, you need to send a welcome email, resize their photo, notify the CRM, and update analytics. Doing all this synchronously makes signup slow and brittle. RabbitMQ decouples operations: your API publishes a message and returns immediately. Workers process jobs asynchronously, independently, and reliably.

💡

API handler = producer. Background service = consumer. RabbitMQ = the reliable broker between them that persists messages, routes to the right queues, and acknowledges completion.

Core Concepts

ConceptWhat it is
ExchangeRouting engine — receives messages and routes them to queues based on rules
QueueBuffer that stores messages until a consumer picks them up
BindingRule connecting an exchange to a queue (with optional routing key)
Acknowledgement (ACK)Consumer signals "I processed this successfully" — message deleted from queue
NACKConsumer signals "I failed" — message can be requeued or sent to dead letter
DLX (Dead Letter Exchange)Where failed messages go for inspection and retry

Exchange Types

ExchangeRoutingUse case
directExact routing key matchRoute to specific queue: "email", "resize"
fanoutBroadcast to all bound queuesNotify ALL consumers: "user.created"
topicWildcard matching (order.*, *.error)Flexible routing: "order.placed", "order.shipped"

Node.js Implementation (amqplib)

npm install amqplib

import amqp from 'amqplib';

// ── Producer ─────────────────────────────────────────────────────
const conn = await amqp.connect(process.env.RABBITMQ_URL);
const ch = await conn.createChannel();

await ch.assertQueue('emails', {
  durable: true,
  arguments: { 'x-dead-letter-exchange': 'dlx' }
});

ch.sendToQueue('emails',
  Buffer.from(JSON.stringify({ to: 'user@example.com', template: 'welcome', userId: 123 })),
  { persistent: true }  // Survive RabbitMQ restart
);

// ── Consumer ─────────────────────────────────────────────────────
await ch.assertQueue('emails', { durable: true });
ch.prefetch(1);  // Process one message at a time

ch.consume('emails', async (msg) => {
  if (!msg) return;
  try {
    const job = JSON.parse(msg.content.toString());
    await sendEmail(job.to, job.template);
    ch.ack(msg);              // Success — remove from queue
  } catch (err) {
    ch.nack(msg, false, false); // Failure — send to dead letter
  }
});

Dead Letter Queue & Management UI

// Set up Dead Letter Exchange + Queue
await ch.assertExchange('dlx', 'direct', { durable: true });
await ch.assertQueue('emails.failed', { durable: true });
await ch.bindQueue('emails.failed', 'dlx', 'emails');
// Failed messages now accumulate in emails.failed for inspection
📊 Management UI

Access RabbitMQ's management dashboard at http://localhost:15672 (user/password in dev). View queues, message rates, consumer count, and manually requeue failed messages — invaluable for debugging.

Check Your Understanding

1. A message is published when no consumer is running. Queue is durable, message is persistent. What happens when the consumer starts?
2. You want to notify ALL services (email, analytics, CRM) when a user signs up. Which exchange type?
3. What does channel.prefetch(1) do?