Reliable Background Task Execution using BullMQ and Node.js

Reliable Background Task Execution using BullMQ and Node.js

In modern Node.js applications, ensuring smooth background processing is crucial for maintaining responsiveness and scalability. Message queues offer an effective solution, decoupling message production from consumption and enabling asynchronous task handling. BullMQ, a popular choice, provides a robust and efficient way to implement message queues in your Node.js projects. This article guides you through integrating BullMQ for reliable background processing

Understanding Message Queues

Imagine a conveyor belt, producers place tasks on the belt (queue), and separate worker processes (consumers) pick them up for execution. This asynchronous approach decouples producers and consumers, allowing them to operate independently without hindering each other's performance. Message queues offer significant advantages:

  • Scalability: Effortlessly scale producers and consumers to manage increasing workloads.

  • Resilience: Tasks persist in the queue, ensuring delivery even if producers or consumers encounter temporary outages.

  • Asynchronous Processing: Producers don't have to wait for tasks to finish processing, leading to a more responsive application.

  • Fault Tolerance: Queues automatically retry tasks upon failures, enhancing system reliability

Getting Started with BullMQ

  1. Installation:

    Begin by installing the bullmq and ioredis packages using npm:

     npm install --save bullmq ioredis

    bullmq provides core functionalities for managing queues and jobs, while ioredis acts as the Redis client library.

  2. Connecting to Redis:

    We are going to use a self-hosted Redis server, download redis from here and install it in your system

     const Redis = require('ioredis');
     const redisConfig = 
     const redisConnection = new Redis({
       port: 6379, // Replace with your Redis port if different
       host: '', // Replace with your Redis host if different
     module.exports = redisConnection;

    This code defines the Redis configuration and creates a connection object.

  3. Creating a Queue:

    Import the Queue class from BullMQ and your Redis connection in your main application file (e.g., index.js)

     import {Queue} from 'bullmq';
     const commentNotificationQueue = new Queue('comment-email-notification', 
     {connection: redisConnection});

    Here, you create a new BullMQ queue named comment-email-notification. The connection property specifies the Redis connection established earlier.

Adding Jobs to the Queue

  1. Job Data:

    Jobs in BullMQ consist of data you want to process asynchronously. This data can be anything relevant to your task, such as file paths, user IDs, or API request parameters.

  2. Adding a Job:

    Use the add method on your queue instance to add a new job:

     async function addJobs() {
         const comments = [
                 "text": "I really enjoyed your article on machine learning! The explanation of different algorithms was very clear and concise. Keep up the good work!",
                 "commenter_name": "John Smith",
                 "commenter_email": "", "author_name": "Jane Doe",
                 "author_email": ""
                 "text": "This phone is amazing! The battery life is fantastic, and the camera takes stunning photos. I would highly recommend it to anyone looking for a new phone.",
                 "commenter_name": "Sarah Lee",
                 "commenter_email": "", "author_name": "Stephan Josh"
                 "text": "Thanks for your insights on the future of AI. I agree that ethical considerations are crucial as this technology advances.",
                 "commenter_name": "David Kim",
                 "commenter_email": "", "author_name": "Michael Brown",
                 "author_email": ""
         for (const comment of comments) {
             await commentNotificationQueue.add(comment?.commenter_name, comment);
     await addJobs();

    This code adds a job named based on commenter's name with the specified data (jobData) to the comment-email-notification queue.

  3. Job Options (Optional):

    BullMQ offers various options for customizing job behaviour:

    • delay: Schedule the job for execution later (in milliseconds).

    • attempts: Define the number of retries for failed jobs.

    • backoff: Set a backoff strategy for retries after failures (e.g., exponential delay).

    • priority: Assign a priority value to jobs (higher values are processed sooner).

These options provide granular control over job processing and error handling.

This is how the jobs are stored in the Redis database:

Consuming Jobs (Workers)

  1. Worker Creation:

    Workers are Node.js processes that continuously listen for new jobs in a queue and execute them. Create a worker using the Worker class:

     const commentNotificationWorker = new Worker('comment-email-notification',
         async job => {
             const {commenter_name, author_name} = job?.data;
             // process comment and send email to author's email about the new comment that he/she receives
             console.log(`${commenter_name} commented on ${author_name} article`)
         }, {connection: redisConnection});

    This code defines a worker for the comment-email-notification queue. The provided asynchronous function (async (job) => {...}) will be executed for each job received by the worker. Remember to replace the placeholder comment with your actual business logic that utilizes the property.

  2. Test the worker:

    This is the output of the worker when the job is executed from the queue

    Here is the link to the source code:

Did you find this article valuable?

Support Gaurav Kumar by becoming a sponsor. Any amount is appreciated!