DEV Community

ANKUSH CHOUDHARY JOHAL
ANKUSH CHOUDHARY JOHAL

Posted on • Originally published at johal.in

How to Implement Cron Jobs with BullMQ 5.0 and Node.js 22

How to Implement Cron Jobs with BullMQ 5.0 and Node.js 22

Cron jobs are essential for automating recurring tasks like sending daily reports, cleaning up databases, or syncing data. While Node.js has libraries like node-cron, they lack persistence and distributed support. BullMQ 5.0, built on Redis, solves this by offering reliable, distributed cron job scheduling with built-in retry, error handling, and worker scaling. This guide walks you through setting up cron jobs with BullMQ 5.0 and Node.js 22.

Prerequisites

  • Node.js 22 or later installed locally
  • Redis server (local or hosted) running
  • Basic understanding of JavaScript and Node.js
  • Familiarity with cron syntax (optional, but helpful)

Step 1: Initialize Project and Install Dependencies

Create a new project directory and initialize it:

mkdir bullmq-cron-demo
cd bullmq-cron-demo
npm init -y
Enter fullscreen mode Exit fullscreen mode

Add "type": "module" to your package.json to use ES module syntax with Node.js 22. Then install required dependencies:

npm install bullmq ioredis dotenv
Enter fullscreen mode Exit fullscreen mode

BullMQ 5.0 requires ioredis as a peer dependency for Redis connections, so we install that explicitly. dotenv loads environment variables from a .env file.

Step 2: Configure Redis Connection

BullMQ uses Redis to persist job data, track schedules, and coordinate workers. Create a redis-client.js file to set up the Redis connection:

import IORedis from 'ioredis';
import dotenv from 'dotenv';

dotenv.config();

const redisClient = new IORedis(process.env.REDIS_URL || 'redis://localhost:6379', {
  maxRetriesPerRequest: null, // Required for BullMQ 5.0
});

redisClient.on('connect', () => console.log('Connected to Redis'));
redisClient.on('error', (err) => console.error('Redis error:', err));

export default redisClient;
Enter fullscreen mode Exit fullscreen mode

Note the maxRetriesPerRequest: null option, which is mandatory for BullMQ 5.0 to function correctly. If using a hosted Redis instance, set the REDIS_URL environment variable in a .env file (e.g., REDIS_URL=redis://user:password@host:port).

Step 3: Create a Cron Job Scheduler

BullMQ’s Queue class supports cron scheduling via the repeat option when adding jobs. Create a scheduler.js file to define and register your cron jobs:

import { Queue } from 'bullmq';
import redisClient from './redis-client.js';

const cronQueue = new Queue('cron-jobs', {
  connection: redisClient,
});

async function addCronJobs() {
  // Add a daily report job that runs at 9 AM UTC every day
  await cronQueue.add(
    'daily-report', // Job name
    { userId: 123, reportType: 'daily' }, // Job payload
    {
      repeat: {
        pattern: '0 9 * * *', // Cron pattern: every day at 9 AM UTC
        tz: 'UTC', // Timezone for the cron pattern
      },
      attempts: 3, // Retry up to 3 times on failure
      backoff: {
        type: 'exponential',
        delay: 1000, // Initial retry delay in ms
      },
      removeOnComplete: true, // Remove job from Redis after completion
      removeOnFail: false, // Keep failed jobs for debugging
    }
  );

  console.log('Cron jobs registered successfully');
  await redisClient.quit();
}

addCronJobs().catch(console.error);
Enter fullscreen mode Exit fullscreen mode

The repeat.pattern uses standard cron syntax. You can also use repeat.every to specify an interval in milliseconds (e.g., every: 86400000 for daily) if you prefer not to use cron patterns. The attempts and backoff options configure automatic retries for failed jobs.

Step 4: Define a Worker to Process Cron Jobs

Workers listen to the queue and process incoming jobs. Create a worker.js file to define job processing logic:

import { Worker } from 'bullmq';
import redisClient from './redis-client.js';

const cronWorker = new Worker(
  'cron-jobs', // Queue name (must match scheduler)
  async (job) => {
    console.log(`Processing job ${job.id}: ${job.name}`);
    console.log('Job payload:', job.data);

    // Add your task-specific logic here
    if (job.name === 'daily-report') {
      // Simulate report generation
      await new Promise((resolve) => setTimeout(resolve, 1000));
      console.log(`Generated daily report for user ${job.data.userId}`);
    }
  },
  {
    connection: redisClient,
    concurrency: 5, // Process up to 5 jobs simultaneously
  }
);

// Event listeners for job lifecycle
cronWorker.on('completed', (job) => {
  console.log(`Job ${job.id} completed successfully`);
});

cronWorker.on('failed', (job, err) => {
  console.error(`Job ${job.id} failed:`, err.message);
});

// Graceful shutdown on process termination
process.on('SIGTERM', async () => {
  console.log('Shutting down worker...');
  await cronWorker.close();
  await redisClient.quit();
  process.exit(0);
});

process.on('SIGINT', async () => {
  console.log('Shutting down worker...');
  await cronWorker.close();
  await redisClient.quit();
  process.exit(0);
});
Enter fullscreen mode Exit fullscreen mode

The worker’s concurrency option controls how many jobs it processes in parallel. Graceful shutdown handlers ensure jobs are not interrupted mid-processing when the worker restarts.

Step 5: Test Your Cron Job Setup

Follow these steps to test the implementation:

  1. Start your local Redis server (or ensure your hosted Redis instance is accessible).
  2. Run the scheduler to register the cron job: node scheduler.js. You should see a success message.
  3. Run the worker in a separate terminal: node worker.js.
  4. For quick testing, update the cron pattern in scheduler.js to * * * * * (runs every minute) to see jobs process immediately. Remember to rerun the scheduler after making changes.

BullMQ stores repeatable job schedules in Redis, so even if you restart the worker or scheduler, the cron job will continue to run as scheduled.

Production Best Practices

  • Use a managed Redis service (e.g., Redis Cloud, AWS ElastiCache) instead of local Redis for high availability.
  • Set removeOnComplete and removeOnFail to manage Redis storage usage, as completed/failed jobs accumulate over time.
  • Monitor queue health using BullMQ’s built-in metrics or tools like BullMQ Board (a web UI for queue management).
  • Run multiple worker instances to scale job processing horizontally.
  • Store all configuration (cron patterns, Redis URLs, etc.) in environment variables, never hardcode them.

Conclusion

BullMQ 5.0 combined with Node.js 22 provides a robust, distributed solution for cron job scheduling that outperforms in-memory alternatives like node-cron. By leveraging Redis for persistence, you get reliable job execution even across service restarts, plus built-in retry logic, error handling, and scaling support. Use this setup to automate recurring tasks in production-grade Node.js applications with confidence.

Top comments (0)