Message Queues with RabbitMQ

A message queue lets one service hand off work to another without waiting for it. The producer drops a message into a broker like RabbitMQ, and a consumer picks it up whenever it is ready — so the two services are decoupled in time, speed, and availability.

Learn Message Queues with RabbitMQ in our free Node.js course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick…

Part of the free Node.js course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

In this lesson you'll learn the core AMQP concepts — connections, channels, queues, exchanges, and acknowledgements — and use the amqplib client to build a durable work queue and a pub/sub broadcast in Node.js.

What You'll Learn in This Lesson

1️⃣ Connect, Channel, and Declare a Queue

Every amqplib program starts the same way: open a connection to the broker, create a lightweight channel over it, and assert the queue you'll use. A channel is where nearly all work happens — you rarely touch the raw connection again. assertQueue is idempotent, so it's safe to call on every startup.

Note durable: true on the queue — that's what lets the queue itself survive a broker restart. Durability is opt-in because not every queue needs to outlive a crash, and durable queues cost a little extra disk work.

2️⃣ Produce and Consume (with Acknowledgements)

A producer drops bytes onto the queue with sendToQueue and moves on. A consumer registers a callback with consume . The crucial part is acknowledgement : after you finish a message you call ack so the broker removes it. If your consumer crashes before acking, the broker redelivers the message — that's at-least-once delivery.

prefetch(1) gives you fair dispatch : the broker won't hand a worker a second message until it acks the first, so a slow job never piles up on one worker while another sits idle. On failure, nack(msg, false, false) rejects the message without requeuing it, so it can route to a dead-letter queue instead of looping forever.

3️⃣ Pub/Sub with a Fanout Exchange

A work queue makes consumers compete for messages. Pub/sub does the opposite: every subscriber gets its own copy . To do that, producers publish to an exchange instead of a queue, and each subscriber binds its own private queue to that exchange. A fanout exchange copies each message to every bound queue.

Each subscriber declares an exclusive queue with an empty name, so the broker generates a unique name and auto-deletes it when the subscriber disconnects. Bind it to the exchange and every broadcast lands in it. Swap fanout for topic when subscribers only want messages matching a routing-key pattern.

Your turn. Fill in the two ___ blanks so the work queue and its messages both survive a broker restart.

Start two copies of the consumer from section 2, both consuming the tasks queue. Publish ten jobs from the producer. Because both workers share the queue and use prefetch(1) , the jobs are divided between them — a fast worker grabs more than a slow one. Confirm that each job is handled by exactly one worker, never both. That's the competing-consumers pattern that lets you scale throughput just by adding processes.

📋 Quick Reference — amqplib

Practice quiz

What is the main reason to put a message queue between two services?

  • To decouple producer and consumer so they can work at different speeds and survive each other being down
  • To replace the database
  • To encrypt all traffic automatically
  • To make HTTP requests faster

Answer: To decouple producer and consumer so they can work at different speeds and survive each other being down. Queues decouple producers from consumers, buffering work and absorbing load and outages.

Which protocol does RabbitMQ implement by default?

  • gRPC
  • MQTT only
  • AMQP 0-9-1
  • HTTP/2

Answer: AMQP 0-9-1. RabbitMQ's core protocol is AMQP 0-9-1 (it also supports MQTT and STOMP via plugins).

In amqplib, what must you create before you can publish or consume?

  • A new TCP socket per message
  • A channel obtained from the connection
  • A worker thread
  • A database pool

Answer: A channel obtained from the connection. You open a connection, then create a channel; nearly all AMQP operations happen on a channel.

What does channel.assertQueue('tasks') do?

  • Deletes the queue if it exists
  • Sends a message to the queue
  • Subscribes a consumer immediately
  • Declares the queue, creating it if it does not already exist

Answer: Declares the queue, creating it if it does not already exist. assertQueue idempotently declares a queue, creating it only if it is not already present.

Why call channel.ack(msg) after processing a message?

  • To tell the broker the message was handled so it can be removed and not redelivered
  • To delete the queue
  • To pause the consumer
  • To create a new exchange

Answer: To tell the broker the message was handled so it can be removed and not redelivered. Acknowledging confirms successful processing; without it the broker may redeliver the message.

What happens if a consumer crashes after receiving a message but before acking it?

  • The message is lost forever
  • The broker redelivers it to another consumer
  • The whole queue is deleted
  • It is silently dropped

Answer: The broker redelivers it to another consumer. Unacked messages are requeued and redelivered, which is why manual ack gives at-least-once delivery.

How do you make a work queue survive a broker restart?

  • Disable acknowledgements
  • Nothing is needed; RabbitMQ never loses messages
  • Use a larger prefetch
  • Declare the queue durable and publish messages as persistent

Answer: Declare the queue durable and publish messages as persistent. Durability needs a durable queue plus persistent messages (deliveryMode 2) to survive restarts.

In the work-queue pattern, what does channel.prefetch(1) achieve?

  • It deletes messages after one second
  • It makes delivery faster by batching
  • It tells the broker to give a worker only one unacked message at a time for fair dispatch
  • It limits the queue to one message total

Answer: It tells the broker to give a worker only one unacked message at a time for fair dispatch. prefetch(1) limits in-flight unacked messages so busy workers are not overloaded (fair dispatch).

To broadcast one message to many consumers (pub/sub), which exchange type do you use?

  • headers
  • fanout
  • direct
  • the default (nameless) exchange

Answer: fanout. A fanout exchange copies each message to every bound queue, giving broadcast pub/sub.

In pub/sub, why does each subscriber typically use its own exclusive, auto-deleted queue?

  • So every subscriber gets its own copy of the broadcast and the queue is cleaned up when it disconnects
  • Because exchanges store messages
  • To avoid declaring an exchange
  • To share a single queue and compete for messages

Answer: So every subscriber gets its own copy of the broadcast and the queue is cleaned up when it disconnects. Each subscriber binds its own temporary queue to the exchange so all receive every message.