Be the Master of Your Microservices with BullMQ: A Guide to Seamless Communication and Queue Management

'Seyi Ogunjuyigbe
4 min readMay 8, 2023

--

Photo by Alci Alliata on Unsplash

As microservices architecture becomes increasingly popular in modern software development, it’s essential to have a reliable messaging system to allow different services to communicate with each other. BullMQ is a powerful message queue library that allows for job processing, communication between microservices, and task scheduling. In this tutorial, we will explore how to use BullMQ with Node.js to communicate between microservices.

### What is BullMQ?

BullMQ is a Redis-based job and message queue library for Node.js. It enables communication between different services by providing a reliable and performant messaging system. BullMQ uses Redis as its storage engine and provides a simple API to create and manage queues, jobs, and tasks.

### Installing Redis

Before we begin, we need to ensure that Redis is installed on our system. Redis is an in-memory data store that provides high performance and is used as a backend storage engine for BullMQ.

To install Redis on your system, you can follow the official Redis installation guide at https://redis.io/topics/quickstart.

### Installing BullMQ

With Redis installed, we can now install BullMQ. You can install BullMQ using npm by running the following command:

npm install bull

### Creating a Queue

To create a new BullMQ queue, we can use the `Queue` class provided by the BullMQ library. Let’s create a new queue called `user-queue`:

const { Queue } = require(‘bull’);
const userQueue = new Queue(‘user-queue’);

We can also specify additional options when creating a new queue, such as the Redis connection options, the prefix for all keys, and the default job options.

const { Queue } = require(‘bull’);
const userQueue = new Queue(‘user-queue’,
{
redis: {
port: 6379,
host: ‘127.0.0.1’,
},
prefix: ‘myapp:’,
defaultJobOptions: {
attempts: 3,
backoff: {
type: ‘exponential’,
delay: 5000,
},
},
});

### Adding Jobs to the Queue

To add a job to the queue, we can use the `add` method provided by the queue instance.

const job = await userQueue.add({
name: ‘John Doe’,
email: ‘johndoe@example.com’,
});

We can also specify additional options when adding a new job, such as the delay, the maximum number of attempts, and the priority of the job.

const job = await userQueue.add(
{
name: ‘John Doe’,
email: ‘johndoe@example.com’,
},
{
delay: 10000, // 10 seconds
attempts: 5,
priority: 1,
});

### Processing Jobs in the Queue

To process jobs in the queue, we can use the `process` method provided by the queue instance.

userQueue.process(async (job) => {
console.log(`Processing job with data: ${JSON.stringify(job.data)}`);
});

We can also specify additional options when processing jobs, such as the number of jobs to process concurrently and the amount of time to wait for new jobs.

userQueue.process(5, // Maximum number of concurrent jobs
async (job) => {
console.log(`Processing job with data: ${JSON.stringify(job.data)}`)
},
{
drainDelay: 5000, // Wait for 5 seconds for new jobs before shutting down the worker
}
)

In a microservice architecture, it is common to have multiple services communicating with each other. For the purpose of this tutorial, we will consider two services: a user service and a payment service. The user service will be responsible for handling user authentication and authorization, while the payment service will be responsible for processing payments.

To pass data between these two services, let’s start by creating a queue in the user service to pass user data to the payment service.

const { Queue } = require(‘bullmq’);
const userQueue = new Queue(‘userQueue’);

In the user service, when a user makes a payment, we can add a job to the `userQueue` with the necessary data:

userQueue.add(‘processPayment’, { userId: 123, amount: 100 });

In the payment service, we can process the job by creating a worker:

const { Worker } = require(‘bullmq’);
const worker = new Worker(‘userQueue’, async job => {
const { userId, amount } = job.data;
// process payment for user with id userId
return { success: true };
});

Here, we have created a worker for the `userQueue` that processes jobs with the type `processPayment`. The worker retrieves the necessary data from the job, processes the payment, and returns a result object.

Tracking Jobs

To track the queues and jobs, we can use the BullMQ Manager. The BullMQ Manager is a web interface that allows you to monitor and manage your queues and jobs.

To use the BullMQ Manager, we need to add the `bull-board` middleware to our express server:

const express = require(‘express’);
const { createBullBoard } = require(‘bull-board’);
const { BullMQAdapter } = require(‘bull-board/bullMQAdapter’);
const app = express();
const bullMQAdapter = new BullMQAdapter(userQueue);
const { router: bullBoardRouter } = createBullBoard({
queues: [bullMQAdapter],
});

app.use(‘/admin/queues’, bullBoardRouter);

Now we can access the BullMQ Manager by navigating to `/admin/queues` in our browser. We can monitor the status of our queues and jobs, view job details, and even retry or delete jobs if necessary.

In this tutorial, we have learned how to use BullMQ to pass data between microservices and how to use the BullMQ Manager to monitor our queues and jobs. With these tools, we can ensure that our microservices are communicating effectively and efficiently.

--

--