node.js hello world

This part is also available in: Python Ruby

You might be familiar with what LavinMQ is already. But to reiterate, we are going to go over it again, but this time around, in an approachable manner.

What is LavinMQ

LavinMQ is a message broker also called a message queue - But what is a message broker?

From a non-technical standpoint, a broker is literally a middle man that facilitates a transaction between two parties, usually a buyer and a seller. Message brokers do pretty much the same thing in the world of software.

LavinMQ and other brokers like it, facilitate the transfer of binary blobs of data, known as messages, between a sending application and a receiving application interested in the message. But how?

  • The sending application, known as a producer, creates and sends messages to LavinMQ. This process is referred to as publishing.

  • As a message broker, LavinMQ receives and securely stores messages from producers until they are retrieved by receiving applications. LavinMQ utilizes its built-in queue that maintains the order of arrival and persists messages until they are picked up.
    • To better get a sense of how a qeueue works in LavinMQ and in fact in most message brokers, you can think of it as a cylinder with open ends. New messages are added to the queue from one end of the cylinder and only removed from the opposite end - First In First Out(FIFO).
  • A receiving application formally called the consumer receives messages from LavinMQ and processes them at its own pace. This process is referred to as consuming.

The image below visualizes the flow of messages from the Producer -> LavinMQ -> Consumer.

LavinMQ First In First Out Queue

Some noteworthy information on producers, consumers and messages: - Producers and consumers can be processes running on the same computer, modules of an application, or services running on different computers with varying technology stacks. - Messages are the data added by the sender application to the queue. They can include task requests for the receiving system, like sending an email or resizing an image, as well as plain text or information about completed tasks.

Now that we get the basics of LavinMQ, let’s see how we can set up a basic producer that publishes messages to LavinMQ and a corresponding consumer that consumes the messages.

Publishing and consuming messages with amqp-client.js, node.js client

node.js developers have a number of options for AMQP client libraries. In this tutorial, amqp-client.js is used, an asynchronous client for publishing and consuming of messages.

We will build the simplest thing you can make with LavinMQ - two rudimentary node.js applications. The first node.js application, the producer will publish messages to LavinMQ and the second application, the consumer will receive these messages from LavinMQ.

There are three steps we will take to build our rudimentary producer and consumer: 1. Setting up the node.js development environment - we will only have this step in this section. For subsequent node.js tutorials, we will reference this guide and have you replicate steps where necessary. 1. Creating the producer 1. Creating the consumer

node.js development environment

  • First make sure that you have node.js and that it’s available from your command line. You can confirm this by running: node --version
  • If you do not have node.js installed, go to nodejs.org
  • Create a directory for your LavinMQ tutorials if it does not exist already. Run mkdir lavinmq_tutorials && cd lavinmq_tutorials to create a directory named lavinmq_tutorials and then navigate into it - this directory will house the tutorial in this section and all the subsequent ones. We will create sub-directories in this folder to organize the different tutorials based on technologies.
  • next create a sub-directory for the node.js tutorials in the node_tutorials directory. Run mkdir node_tutorials && cd node_tutorials to create a sub directory named node_tutorials and then navigate into it
  • While in the node_tutorials sub-directory. Run npm init to initialize that directory as a Nodejs project. Leave all the questions unanswered/empty by pressing enter all the way.
  • Next add @cloudamqp/amqp-client and dotenv as a dependency to your package.json file. This can be done by running the following command in the terminal: npm install --save @cloudamqp/amqp-client dotenv
  • Create a .env file in in the root directory
  • Add CLOUDAMQP_URL="lavinmq_url" to the `.env’ file. Replace lavinmq_url with your correct server url
  • In order for amqp-client to work you need to add “type”: “module” to your package.json just right under "name": "node_tutorials" like so:
{
  "name": "node_tutorials",
  "type": "module",
}

Creating the producer - sending messages

Here, we will create a basic node.js application that publishes a plain text message to LavinMQ - the hello world of LavinMQ.

There are four steps we need to create an application that sends a message to LavinMQ: - First, we need to create a connection to our LavinMQ instance from our node.js code - Next, we create a channel within the connection we’ve created - Next, we declare a queue - Then publish messages to the queue

Let’s see what these steps look like in code

Creating a connection

Create a file hello_world_producer.js in the node_tutorials sub-directory. and add the snippet below to the file:

//Dependencies
import { AMQPClient } from '@cloudamqp/amqp-client'
import {} from 'dotenv/config'

const LavinMQUrl = process.env.CLOUDAMQP_URL

In the snippet above, we imported relevant packages and loaded our LavinMQ connection string from the .env file. Next, let’s create a connection to our LavinMQ server. Add the code snippet below to the end of hello_world_producer.js file.

async function startProducer() {
  try {
    //Setup a connection to the LavinMQ server
    const connection = new AMQPClient(LavinMQUrl)
    await connection.connect()
  } catch (error){

  }
}

In the snippet above, we created a boilerplate function for sending messages to our LavinMQ server. We also created initiated a connection to our LavinMQ instance within the function

Create a channel

In the hello_world_producer.js file, add the snippet below to the end of the try block:

const channel = await connection.channel()
console.log("[✅] Connection over channel established")

Now that we have a connection and a channel, let’s try to make sense of what they are.

If we think of sending a message from a producer to a LavinMQ instance in terms of, let’s say, delivering a package between two houses in a neighbourhood, then we can say a connection is the direct road linking these two houses and a channel, the multiple paths within that road.

In more technical terms, a connection is the direct link between a producer or consumer and a LavinMQ server, giving the producer or consumer access to the LavinMQ server.

Because you are most likely going to be needing to send or receive multiple messages, a channel allows the re-use of a connection - think of channels as smaller connections within a connection - all messages are published and consumed on a channel.

Declare a queue

In the hello_world_producer.js file, add the snippet below to the end of the try block, to declare a queue, named hello_world:

const q = await channel.queue(queue='hello_world', {durable: false})

hello_world in the snippet above is the queue name. In the event that you do not pass a queue name, one would be auto-generated.

Publish messages to the declared queue

We are going to create a re-usable function, sendToQueue that we can use to publish messages to LavinMQ. We will publish three messages and close the connection.

We will create this function within the startProducer function. In the hello_world_producer.js file, add the snippet below to the end of the try block:

//Publish a message to the exchange
async function sendToQueue(routingKey, body) {
  //amqp-client function expects: exchange, routingKey, message, options
  await channel.basicPublish("", routingKey, body)
  console.log("[📥] Message sent to queue", body)
}

//Send some messages to the queue
sendToQueue("hello_world", "Hello World");
sendToQueue("hello_world", "Hello World");
sendToQueue("wrong_routing_key", "Hello World");

setTimeout(() => {
  //Close the connection
  connection.close()
  console.log("[❎] Connection closed")
  process.exit(0)
}, 500);

In the snippet above, the sendToQueue function publishes messages to LavinMQ using the channel.basicPublish() function. body is the message that will be published to the queue, the empty string passed, stands in place of an exchange name that should have been passed to the function. And the routing_key, well, is the routing key. At this point, exchanges and routing keys might not make so much sense to you, but do not worry about details like that. Just know that those parameters are used to route these messages to the appropriate queues. Next, let’s create our consumer.

Lastly, let’s catch errors and start the producer. In the hello_world_producer.js file, add the snippet below to the end of the catch block:

console.error(error)
  //Retry after 3 second
  setTimeout(() => {
    startProducer()
  }, 3000)

The start the producer by adding the line below to the end of your hello_world_producer.js startProducer()

Creating the consumer - receiving messages

Here, we will create a basic node.js application that would receive the plain-text message we will publish and process them.

Again, there are four steps we need to create an application that sends a message to LavinMQ: - First, we need to create a connection to our LavinMQ instance from our node.js code - Next, we create a channel within the connection we’ve created - Next, we declare a queue - Then consume messages from the queue

Let’s see what these steps look like in code. First, create a file hello_world_consumer.js in the node_tutorials sub-directory.

The next step would have been to import relevant modules, create a connection and a channel, but the code for doing that hasn’t changed, so feel free to re-use snippets from the producer section.

The next step is to declare a queue.

Declare a queue

Add the code snippet below to the end of hello_world_consumer.js file, to declare a queue, named hello_world

const q = await channel.queue('hello_world', {durable: false})

Why are we creating the same queue twice -- you ask?

Or maybe you didn’t ask, but still, note that creating a queue is idempotent - regardless of how many times we run the command, only one queue will be created.

But even more importantly, we are creating the same queue from the consumer side again, just in case you start the consumer first, at which point the hello_world queue declared from the producer side will be non-existent.

Consume messages from the declared queue

Add the code snippet below to the end of hello_world_consumer.js file, to subscribe to the queue we’ve declared and have our consumer continuously listen and receive messages published to that queue.

Other than starting the consumer, everything else will be done in a function, startConsumer()

async function startConsumer() {
  let counter = 0;

  await q.subscribe({noAck: true}, async (msg) => {
    try {
      console.log(`[📤] Message received (${++counter})`, msg.bodyToString())
    } catch (error) {
      console.error(error)
    }
  })

  //When the process is terminated, close the connection
  process.on('SIGINT', () => {
    channel.close()
    connection.close()
    console.log("[❎] Connection closed")
    process.exit(0)
  });
}
startConsumer().catch(console.error);

The q.subscribe() block is now receiving messages from the queue, in an attempt to process the information. This is where whatever custom logic one decides to work on could be added - in our case, we just log the messages received to the console.

Putting everything together

We’ve worked on the different parts of the producer and consumer in bits, now let’s see what the code would look like when put together.

hello_world_producer.js

//Dependencies
import { AMQPClient } from '@cloudamqp/amqp-client'
import {} from 'dotenv/config'

const cloudAMQPURL = process.env.CLOUDAMQP_URL

async function startProducer() {
  try {
    //Setup a connection to the RabbitMQ server
    const connection = new AMQPClient(cloudAMQPURL)
    await connection.connect()
    const channel = await connection.channel()

    console.log("[✅] Connection over channel established")

    await channel.queue('hello_world', {durable: false})

    //Publish a message to the exchange
    async function sendToQueue(routingKey, body) {
      await channel.basicPublish("", routingKey, body)
      console.log("[📥] Message sent to queue", body)
    }
  
    //Send some messages to the queue
    sendToQueue("hello_world", "Hello World");
    sendToQueue("hello_world", "Hello World");
    sendToQueue("wrong_routing_key", "Hello World");
  
    setTimeout(() => {
      //Close the connection
      connection.close()
      console.log("[❎] Connection closed")
      process.exit(0)
    }, 500);
  } catch (error) {
    console.error(error)
    //Retry after 3 second
    setTimeout(() => {
      startProducer()
    }, 3000)
  }
}

startProducer()

hello_world_consumer.js

import { AMQPClient } from '@cloudamqp/amqp-client'
import {} from 'dotenv/config'

const lavinmqUrl = process.env.CLOUDAMQP_URL

async function startConsumer() {
  //Setup a connection to the RabbitMQ server
  const connection = new AMQPClient(lavinmqUrl)
  await connection.connect()
  const channel = await connection.channel()

  console.log("[✅] Connection over channel established")
  console.log("[❎] Waiting for messages. To exit press CTRL+C ")

  const q = await channel.queue('hello_world', {durable: false})

  let counter = 0;

  await q.subscribe({noAck: true}, async (msg) => {
    try {
      console.log(`[📤] Message received (${++counter})`, msg.bodyToString())
    } catch (error) {
      console.error(error)
    }
  })

  //When the process is terminated, close the connection
  process.on('SIGINT', () => {
    channel.close()
    connection.close()
    console.log("[❎] Connection closed")
    process.exit(0)
  });
}

startConsumer().catch(console.error);

Testing our appplications

Spin up two terminals. In the first terminal, run your consumer application with node hello_world_consumer.js

In the second terminal run your producer application with: node hello_world_producer.js

If everything goes well, your output should look like what’s shown in the below

Terminal 1 - Consumer

->$ node hello_world_consumer.js
[✅] Connection over channel established
[❎] Waiting for messages. To exit press CTRL+C 
[📤] Message received (1) 
[📤] Message received (2) 

Terminal 2 - Producer

->$ node hello_world_producer.js
[✅] Connection over channel established
[📥] Message sent to queue Hello World
[📥] Message sent to queue Hello World
[📥] Message sent to queue Hello World
[❎] Connection closed

A few things to note

Eeven though we closed connection to the LavinMQ server in the publisher file, in reality, it’s best to keep the connection alive in both the publisher and consumer scripts, rather than opening and closing it repeatedly. This is because opening and closing connections is considered “expensive”.

You might notice that the consumer only received two out of the three messages we sent. Any idea what the bug is? Well, that’s one of your tasks in this section.

Learning lab

  • Debug the code above and get all three messages to be published to the queue and by extension received by the consumer.
    • Hint : The problem is precisely with the line publishing the third message
  • Optional : What are exchanges, bindings and routing keys
    • You don’t have to look into this lab, but learning about these concepts would help you follow the next section more easily.

What’s next?

Ready to take the next steps? Here are some things you should keep in mind:

Managed LavinMQ instance on CloudAMQP

LavinMQ has been built with performance and ease of use in mind - we've benchmarked a throughput of about 1,000,000 messages/sec. You can try LavinMQ without any installation hassle by creating a free instance on CloudAMQP. Signing up is a breeze.

Help and feedback

We welcome your feedback and are eager to address any questions you may have about this piece or using LavinMQ. Join our Slack channel to connect with us directly. You can also find LavinMQ on GitHub.