Your First Queue
The smallest useful example
Section titled “The smallest useful example”Let’s send one message through RabbitMQ. To keep it minimal we’ll use the default exchange — a nameless direct exchange that RabbitMQ provides automatically, where the routing key is simply the queue name. That lets us “publish straight to a queue” without declaring an exchange yet. (Every later lesson uses real named exchanges.)
First, run a broker locally with Docker:
docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-managementPort 5672 is AMQP (for your app); 15672 is the management UI (open http://localhost:15672, log in with guest/guest).
The producer: publish one message
Section titled “The producer: publish one message”The producer connects, opens a channel, declares a queue (so it exists), and publishes a message to it.
import amqp from 'amqplib';
const conn = await amqp.connect('amqp://localhost');const channel = await conn.createChannel();
const queue = 'hello';await channel.assertQueue(queue, { durable: false });
channel.sendToQueue(queue, Buffer.from('Hello, RabbitMQ!'));console.log('sent: Hello, RabbitMQ!');
await channel.close();await conn.close();import pika
conn = pika.BlockingConnection(pika.ConnectionParameters("localhost"))channel = conn.channel()
channel.queue_declare(queue="hello", durable=False)
channel.basic_publish(exchange="", routing_key="hello", body="Hello, RabbitMQ!")print("sent: Hello, RabbitMQ!")
conn.close()conn, _ := amqp.Dial("amqp://guest:guest@localhost:5672/")defer conn.Close()ch, _ := conn.Channel()defer ch.Close()
q, _ := ch.QueueDeclare("hello", false, false, false, false, nil)
ch.PublishWithContext(ctx, "", q.Name, false, false, amqp.Publishing{ Body: []byte("Hello, RabbitMQ!"),})log.Println("sent: Hello, RabbitMQ!")Note the empty exchange string ("") — that’s the default exchange, and the routing key is the queue name.
The consumer: receive it
Section titled “The consumer: receive it”The consumer connects, declares the same queue (declaring is idempotent — safe to do on both sides), and subscribes. Its callback fires for each delivered message.
import amqp from 'amqplib';
const conn = await amqp.connect('amqp://localhost');const channel = await conn.createChannel();
const queue = 'hello';await channel.assertQueue(queue, { durable: false });
console.log('waiting for messages...');channel.consume(queue, (msg) => { if (msg) { console.log('received:', msg.content.toString()); channel.ack(msg); }});import pika
conn = pika.BlockingConnection(pika.ConnectionParameters("localhost"))channel = conn.channel()channel.queue_declare(queue="hello", durable=False)
def on_message(ch, method, properties, body): print("received:", body.decode()) ch.basic_ack(delivery_tag=method.delivery_tag)
channel.basic_consume(queue="hello", on_message_callback=on_message)print("waiting for messages...")channel.start_consuming()conn, _ := amqp.Dial("amqp://guest:guest@localhost:5672/")defer conn.Close()ch, _ := conn.Channel()defer ch.Close()
q, _ := ch.QueueDeclare("hello", false, false, false, false, nil)msgs, _ := ch.Consume(q.Name, "", false, false, false, false, nil)
log.Println("waiting for messages...")for d := range msgs { log.Printf("received: %s", d.Body) d.Ack(false)}What just happened
Section titled “What just happened”flowchart LR
p["Producer
sendToQueue('hello')"] -->|default exchange| q["Queue: hello"]
q -->|deliver| c["Consumer
prints + acks"] The important details, small as they are:
- Both sides declare the queue. Declaring is idempotent and ensures the queue exists no matter who starts first.
- The message is just bytes. RabbitMQ doesn’t care about your format — string, JSON, protobuf, anything. Serialization is your job.
- The consumer acks. After processing, it tells RabbitMQ “done” so the message is removed. Skip this and you’ll get the message again on reconnect (which is exactly the safety you want — more in the acknowledgements lesson).