RabbitMQ is an open-source message broker that makes communication between services very easy. In particular, RabbitMQ uses a Publish/Subscribe pattern with an Advanced Message Queuing Protocol.

This reduces the load on web app servers and their delivery times because it efficiently delegates resource-intense tasks to third parties with no other tasks.

In this article, we're going to set up RabbitMQ with Docker Compose. Then, we're going to write a message Sender and Receiver using Go. Before we start, make sure you have the following installed:

Setting Up Docker Compose

If you want to make your code more portable and share the same version of RabbitMQ with your developer colleagues, I highly recommend using Docker.

In this case, we're going to use docker-compose to configure the container name, the volumes and networks, and the ports that RabbitMQ will use. Doing so ensures that everything is isolated and easy to modify.

To start, create a folder called rabbitmq-go in your Golang project folder. Then, create a new file with the name docker-compose.yml. Inside that file, add the following:

version: "3.2"
services:
  rabbitmq:
    image: rabbitmq:3-management-alpine
    container_name: 'rabbitmq'
    ports:
        - 5672:5672
        - 15672:15672
    volumes:
        - ~/.docker-conf/rabbitmq/data/:/var/lib/rabbitmq/
        - ~/.docker-conf/rabbitmq/log/:/var/log/rabbitmq
    networks:
        - rabbitmq_go_net

networks:
  rabbitmq_go_net:
    driver: bridge

Here's what we've just done:

  • image: where we tell Docker which image to pull. We're using an Alpine implementation of RabbitMQ with the management plugin. The Alpine distro is the one you'll want to use if you want to save disk space.
  • container_name: this represents the container created from the image above.
  • ports: the list of ports that will be mapped from the container to the outside world, for interacting with the queue and the web UI.
  • volumes: where we map the log and data from the container to our local folder. This allows us to view the files directly in their local folder structure instead of having to connect to the container.
  • networks: where we specify the network's name that the container will be using. This helps to separate container network configurations.

Now that we have this all set up, we can check if RabbitMQ is working correctly. Open a terminal, navigate to your rabbitmq-go folder and run docker-compose up.

This command will pull the rabbitmq:3-management-alpine image, create the container rabbitmq and start the service and webUI. You should see something like this:

Once you see this, open your browser and head over to http://localhost:15672. You should see the RabbitMQ UI. Use guest as username and password.

Congratulations! You've just started RabbitMQ from a Docker image.

Publishing a Message With Go

Now that RabbitMQ is ready to Go, let's connect to it and send a message to the queue. But first, we need the amqp library. To install it, run the following command in your terminal: go get github.com/streadway/amqp.

Then, create a filed called sendMessage.go inside the rabbitmq-go directory. Add the following:

package main

import (
	"log"

	"github.com/streadway/amqp"
)

// Here we set the way error messages are displayed in the terminal.
func failOnError(err error, msg string) {
	if err != nil {
		log.Fatalf("%s: %s", msg, err)
	}
}

func main() {
	// Here we connect to RabbitMQ or send a message if there are any errors connecting.
	conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
	failOnError(err, "Failed to connect to RabbitMQ")
	defer conn.Close()

	ch, err := conn.Channel()
	failOnError(err, "Failed to open a channel")
	defer ch.Close()

	// We create a Queue to send the message to.
	q, err := ch.QueueDeclare(
		"golang-queue", // name
		false,          // durable
		false,          // delete when unused
		false,          // exclusive
		false,          // no-wait
		nil,            // arguments
	)
	failOnError(err, "Failed to declare a queue")

	// We set the payload for the message.
	body := "Golang is awesome - Keep Moving Forward!"
	err = ch.Publish(
		"",     // exchange
		q.Name, // routing key
		false,  // mandatory
		false,  // immediate
		amqp.Publishing{
			ContentType: "text/plain",
			Body:        []byte(body),
		})
	// If there is an error publishing the message, a log will be displayed in the terminal.
	failOnError(err, "Failed to publish a message")
	log.Printf(" [x] Congrats, sending message: %s", body)
}

Once we've created the main.go file, let's test sendMessage with the following terminal command: go run sendMessage.go. You should see something like this:

When you head over to http://localhost:15672, you should see golang-queue in the Queues tab along with the number of messages that have been sent to it.

Awesome! Pat yourself on the back. You've just created a queue and sent a message to it. Now let's learn how to read those messages.

Reading Messages With Go

Create a file called consumer.go. Inside the file, add the following:

package main

import (
	"log"

	"github.com/streadway/amqp"
)

func failOnError(err error, msg string) {
	if err != nil {
		log.Fatalf("%s: %s", msg, err)
	}
}

func main() {
	conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
	failOnError(err, "Failed to connect to RabbitMQ")
	defer conn.Close()

	ch, err := conn.Channel()
	failOnError(err, "Failed to open a channel")
	defer ch.Close()

	q, err := ch.QueueDeclare(
		"golang-queue", // name
		false,          // durable
		false,          // delete when unused
		false,          // exclusive
		false,          // no-wait
		nil,            // arguments
	)
	failOnError(err, "Failed to declare a queue")

	msgs, err := ch.Consume(
		q.Name, // queue
		"",     // consumer
		true,   // auto-ack
		false,  // exclusive
		false,  // no-local
		false,  // no-wait
		nil,    // args
	)
	failOnError(err, "Failed to register a consumer")

	forever := make(chan bool)

	go func() {
		for d := range msgs {
			log.Printf("Received a message: %s", d.Body)
		}
	}()

	log.Printf(" [*] Waiting for messages. To exit press CTRL+C")
	<-forever
}

Now that consume.go is created, let's run it with the following command: go run consume.go. The <-forever line at the end of the file means we'll keep listening to the channel for new messages.

We've made a lot of progress so far. But our sending messages functionality isn't dynamic right now, so let's change that.

Add the following code to the top of the sendMessage.go file, right after func main() {.

// Let's catch the message from the terminal.
reader := bufio.NewReader(os.Stdin)
fmt.Println("What message do you want to send?")
mPayload, _ := reader.ReadString('\n')

Here, we're creating a new Reader to catch the input from the terminal. This makes sending a message more dynamic. Let's test it with go run sendMessage.go.

The beauty of using RabbitMQ is that you can send messages using Go, but read them with Python, JavaScript, PHP, Java, and more! Here's everything in action:

You now know how to spin up a docker image, start RabbitMQ, dynamically send messages to the queue, and read messages from the queue. Bravo! You have everything you need to create amazing applications that efficiently send and receive messages.

Find the files we've created in this tutorial right here.