Skip to content

audoir/rabbitmq-tutorial

Repository files navigation

🐇 RabbitMQ Tutorial

An interactive Next.js application for learning RabbitMQ exchange types. Each exchange type has a dedicated tutorial page with explanations, a code structure overview, and a live demo you can run against a real RabbitMQ instance.

Prerequisites


1. Set Up RabbitMQ with Docker

Option A: Quick start (single command)

docker run -d \
  --name rabbitmq \
  -p 5672:5672 \
  -p 15672:15672 \
  rabbitmq:management

Option B: With persistent data (recommended)

docker run -d \
  --name rabbitmq \
  -p 5672:5672 \
  -p 15672:15672 \
  -v rabbitmq_data:/var/lib/rabbitmq \
  --hostname rabbitmq-host \
  rabbitmq:management

Option C: Using Docker Compose

Create a docker-compose.yml file:

version: "3.8"
services:
  rabbitmq:
    image: rabbitmq:management
    container_name: rabbitmq
    ports:
      - "5672:5672"   # AMQP protocol port
      - "15672:15672" # Management UI port
    volumes:
      - rabbitmq_data:/var/lib/rabbitmq
    environment:
      RABBITMQ_DEFAULT_USER: guest
      RABBITMQ_DEFAULT_PASS: guest

volumes:
  rabbitmq_data:

Then run:

docker compose up -d

Verify RabbitMQ is running

Open the Management UI: http://localhost:15672

  • Username: guest
  • Password: guest

You should see the RabbitMQ dashboard. The AMQP port 5672 is used by the application.

Useful Docker commands

# Check if RabbitMQ is running
docker ps | grep rabbitmq

# View logs
docker logs rabbitmq

# Stop RabbitMQ
docker stop rabbitmq

# Start again
docker start rabbitmq

# Remove container (data lost unless using volume)
docker rm -f rabbitmq

2. Set Up the Next.js App

# Install dependencies
npm install

# Start the development server
npm run dev

Open http://localhost:3000 to see the tutorial home page.

The home page shows a live connection status indicator — green means RabbitMQ is reachable.


3. Environment Variables (Optional)

By default the app connects to amqp://guest:guest@localhost:5672.

To use a different RabbitMQ instance, create a .env.local file:

RABBITMQ_URL=amqp://username:password@your-host:5672

4. How the Code is Structured

Each exchange type follows a strict Setup → Publish → Consume pattern. The responsibilities are split across three separate API routes:

Route Responsibility
setup/route.ts Creates the exchange, queues, and bindings. Must be called first.
publish/route.ts Publishes a message to the exchange. Assumes setup has been done.
consume/route.ts Reads messages from a queue. Assumes setup has been done.

Why? In RabbitMQ, messages are routed to queues at the moment of publishing. If a queue doesn't exist yet (or isn't bound to the exchange), the message is silently dropped. The setup step ensures all queues and bindings exist before any messages are sent.


5. Tutorial Walkthrough

Follow the tutorials in order for the best learning experience. On each page, always click Setup first before publishing.

Step 1 — Fanout Exchange (/tutorial/fanout)

A fanout exchange broadcasts every message to all bound queues. The routing key is ignored.

Try it:

  1. Open http://localhost:3000/tutorial/fanout
  2. Click Setup Exchange & Queues
  3. Click Publish to send a message
  4. Consume from fanout.queue.1 — you get the message
  5. Consume from fanout.queue.2 — you get the same message again!

Key concept: One publish → all queues receive a copy.


Step 2 — Direct Exchange (/tutorial/direct)

A direct exchange routes messages to queues whose binding key exactly matches the routing key.

Try it:

  1. Open http://localhost:3000/tutorial/direct
  2. Click Setup Exchange & Queues
  3. Publish with routing key error
  4. Consume with routing key error → you get the message ✓
  5. Consume with routing key info → you get nothing ✗

Key concept: Exact match routing — like a postal address.


Step 3 — Topic Exchange (/tutorial/topic)

A topic exchange routes using wildcard pattern matching on dot-separated routing keys.

  • * matches exactly one word
  • # matches zero or more words

Try it:

  1. Open http://localhost:3000/tutorial/topic
  2. Click Setup Exchange & Queues
  3. Publish with key logs.error
  4. Publish with key logs.error.critical
  5. Consume with pattern logs.* → gets only logs.error (one word after logs)
  6. Consume with pattern logs.# → gets both messages (any words after logs)

Key concept: Flexible pattern-based routing.


Step 4 — Headers Exchange (/tutorial/headers)

A headers exchange routes based on message header attributes. The routing key is ignored.

  • x-match: all — ALL specified headers must match (AND logic)
  • x-match: any — ANY specified header must match (OR logic)

Try it:

  1. Open http://localhost:3000/tutorial/headers
  2. Click Setup Exchange & Queues
  3. Publish with headers {"format":"pdf","type":"report"}
  4. Consume with x-match=all and headers {"format":"pdf","type":"report"} → match ✓
  5. Consume with x-match=all and headers {"format":"pdf","type":"invoice"} → no match ✗
  6. Consume with x-match=any and headers {"format":"pdf","type":"invoice"} → match ✓ (format matches)

Key concept: Attribute-based routing without routing keys.


Step 5 — Dead Letter Queue (/tutorial/deadletter)

A Dead Letter Queue (DLQ) captures messages that cannot be processed.

Messages become "dead letters" when:

  • A consumer rejects them with nack(msg, false, false) (requeue=false)
  • They expire (TTL elapsed)
  • The queue exceeds its length limit

Try it (rejection):

  1. Open http://localhost:3000/tutorial/deadletter
  2. Click Setup Exchanges & Queues
  3. Click Publish to Main Queue
  4. Click Reject Message (NACK) — simulates a failed consumer
  5. Click Consume from DLQ — see the rejected message with x-death metadata

Try it (TTL expiry):

  1. After setup, publish with TTL = 3000ms
  2. Wait 3 seconds (don't reject)
  3. Click Consume from DLQ — message expired and moved automatically

Key concept: Never lose a message — capture failures in a DLQ for inspection and retry.


6. Project Structure

app/
├── page.tsx                    # Tutorial home page
├── api/
│   ├── status/route.ts         # GET: check RabbitMQ connection
│   ├── fanout/
│   │   ├── setup/route.ts      # POST: create exchange, queues, bindings
│   │   ├── publish/route.ts    # POST: publish to fanout exchange
│   │   └── consume/route.ts    # GET: consume from a named queue
│   ├── direct/
│   │   ├── setup/route.ts      # POST: create exchange, queues, bindings
│   │   ├── publish/route.ts    # POST: publish with routing key
│   │   └── consume/route.ts    # GET: consume by routing key
│   ├── topic/
│   │   ├── setup/route.ts      # POST: create exchange, queues, bindings
│   │   ├── publish/route.ts    # POST: publish with dot-separated key
│   │   └── consume/route.ts    # GET: consume with wildcard pattern
│   ├── headers/
│   │   ├── setup/route.ts      # POST: create exchange, queues, bindings
│   │   ├── publish/route.ts    # POST: publish with custom headers
│   │   └── consume/route.ts    # GET: consume with x-match filter
│   └── deadletter/
│       ├── setup/route.ts      # POST: create both exchanges, both queues, bindings
│       ├── publish/route.ts    # POST: publish to main queue (optional TTL)
│       ├── reject/route.ts     # POST: NACK a message → routes to DLX
│       └── consume/route.ts    # GET: consume from dead letter queue
└── tutorial/
    ├── fanout/page.tsx         # Fanout tutorial page
    ├── direct/page.tsx         # Direct tutorial page
    ├── topic/page.tsx          # Topic tutorial page
    ├── headers/page.tsx        # Headers tutorial page
    └── deadletter/page.tsx     # Dead letter queue tutorial page

lib/
└── rabbitmq.ts                 # Shared: createConnection / closeConnection

7. Exchange Type Quick Reference

Type Routing Logic Routing Key? Best For
Fanout All bound queues ❌ Ignored Broadcasting, notifications
Direct Exact key match ✅ Exact match Log levels, task routing
Topic Pattern matching ✅ Wildcards (* #) Flexible routing, microservices
Headers Header attributes ❌ Ignored Attribute-based routing

8. RabbitMQ Concepts

AMQ Model

Publisher → Exchange → Queue → Consumer
  • Publisher sends messages to an exchange (never directly to a queue)
  • Exchange routes messages to queues based on type and binding rules
  • Queue stores messages until a consumer retrieves them
  • Consumer receives messages; load is balanced across multiple consumers

Supported Protocols

  • AMQP (used by this tutorial) — port 5672
  • STOMP, MQTT, RabbitMQ Streams

Queue Configuration

  • durable — survives broker restart
  • auto-delete — deleted when last consumer disconnects
  • exclusive — only accessible by the declaring connection

Dead Letter Headers (x-death)

When a message is dead-lettered, RabbitMQ adds an x-death header containing:

  • queue — original queue name
  • reasonrejected, expired, or maxlen
  • time — when it was dead-lettered
  • exchange — original exchange
  • routing-keys — original routing keys

9. Troubleshooting

"RabbitMQ Not Connected" on the home page

  • Make sure Docker is running: docker ps | grep rabbitmq
  • Start RabbitMQ: docker start rabbitmq
  • Check logs: docker logs rabbitmq

Port already in use

# Check what's using port 5672
lsof -i :5672
# Or use different ports
docker run -d --name rabbitmq -p 5673:5672 -p 15673:15672 rabbitmq:management
# Then set RABBITMQ_URL=amqp://guest:guest@localhost:5673 in .env.local

Queue already exists with different parameters

  • Go to the Management UI (http://localhost:15672)
  • Navigate to Queues → delete the conflicting queue
  • Or restart RabbitMQ: docker restart rabbitmq

Messages not appearing after publish

  • Make sure you clicked Setup before publishing. If the queues didn't exist at publish time, the messages were silently dropped by RabbitMQ.
  • Click Setup again (it's idempotent), then publish again.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages