Best Node.JS Schedulers
Node.JS Job Schedulers

Best Node.JS Schedulers

Dead Simple Chat Team

Table of Contents

Dead Simple Chat offers prebuilt Chat that can be added in minutes to any app or website. Can be completely customized and offers powerful JavaScript API and SDK.

In this article, we will compare the top Node.JS Cron Scheduler libraries and we will go through their pros and cons.

What is a Job Scheduler

A job scheduler allows us to run a job or a task at regular intervals or at a specified time.

In the case of a Node.JS Scheduler, it would allow us to trigger a JavaScript function at our specified time or interval.

Some examples include:

  • If you want to trigger a function that sends an email every day at 12:00 AM
  • Trigger a function every 10 minutes to cache the analytics data
  • Trigger function every Monday and Friday
  • Trigger a function every year on Dec 25th
💡
Add Pre-built Chat to your app or website, with support for Group, Live Stream and 1-1 Chat with Dead Simple Chat.

In this blog post, we will take a look at the following Job Schedulers. These are the most popular Node.JS Job Schedulers on GitHub and npm.

  • Agenda: As of 2023-Jan-01 it has 8.6k stars on Github and 52,297 weekly downloads on npm.
  • Bull: As of 2023-Jan-01 it has 13.5k stars on Github and 239,429 weekly downloads on npm.
  • Bree: As of 2023-Jan-01 it has 2.5k stars on Github and 12,745 weekly downloads on npm
  • Node Cron As of 2023-Jan-01 it has 2.5k stars on Github, 239,508 weekly downloads on npm

Evaluation Criteria

We will evaluate each job scheduler under the following criteria:

  • Support for Scheduling: Does the Job Queue allows us to run the job at a specified date and time? For e.g: "Run this job on Jan 25th, 2023 at 5 am".
  • Support for Recurrent Jobs: Does the Job Queue allows us to run the job at a specified interval?  For e.g: "Run this job every day at 5pm" or "Run this job on the 1st of every month"
  • Support for Persistence: Does the Job Queue persists and stay on the Job Schedule after the process restart? For e.g: If we create a job "Run this job every 60 minutes" and after 50 minutes and due to any reason our program restarts the job should execute after 10 mins, and it should not reset after the program restart.
  • Horizontal Scaling: Does the Job Queue supports job multiple processors across multiple hosts.  
  • Locking Jobs and Prevention of Duplicate Job Execution: If we have multiple processes running of the Job Queue the same job should not be executed by multiple processes. For e.g: If we have 4 instances of our job queue running in a multi-process or multi-threaded environment, and we create a Job "Send Email to Customers every day at 6pm" then any one instance of our Job queue should pick up and process the job and not all 4.  
Agenda (https://github.com/agenda/agenda)

1. Agenda

Agenda is optimised for Jobs, and it uses MongoDB backend for job persistence.

You need a MongoDB database installed for Agenda to work.

Installing Agenda is a very simple just run:

npm install @hokify/agenda

Let's evaluate Agenda through all our Job Scheduler evaluation criteria

Support for Scheduling ✅

Agenda supports scheduling, creating scheduled jobs in agenda is very simple and intuitive.

You can schedule the jobs to run at a specified date and time, very easily.

To define and schedule job use the schedule method, here is an example:

const mongoConnectionString = 'mongodb://127.0.0.1/agenda';
## Creating agenda instance and specifying it MongoDB database connection string
const agenda = new Agenda({db: {address: mongoConnectionString}});

## Defining a Job
agenda.define('sendHappyBirthdayEmail', async job => {
 await sendHappyBirthdayEmail();
});

(async function() {
	await agenda.start();
    
    await agenda.schedule("tomorrow at noon", ["sendHappyBirthdayEmail"]);
    
})();

You can also pass the schedule method a Date object

await agenda.schedule(new Date(628021800000), ["sendHappyBirthdayEmail"])

You can also schedule multiple jobs at once

await agenda.schedule(new Date(628021800000), ["jobA","jobB","sendHappyBirthdayEmail"])

Support for Recurrent Jobs ✅

Agenda also supports recurrent jobs. You can define jobs that run every day, every month etc.

To create a recurrent job use the every method

await agenda.every("12 hours", "sendReport");

You can also specify in corn format

await agenda.every("*/2 * * * *", "notifyUsers");

Here is a complete example:

const mongoConnectionString = 'mongodb://127.0.0.1/agenda';
## Creating agenda instance and specifying it MongoDB database connection string
const agenda = new Agenda({db: {address: mongoConnectionString}});

## Defining a Job
agenda.define('sendReport', async job => {
 await generateAndEmailReport();
});

(async function() {
	await agenda.start();
    
    await agenda.every("21 hours", ["sendReport"]);
    
})();

Support for Persistence ✅

Agenda supports persistence. As it uses MongoDB, the jobs are stored in the MongoDB database and the job information remains persistent even after the Node.JS  process restart or after a crash.

Horizontal Scaling ✅

Agenda has a MongoDB backend, through the MongoDB backend you can start multiple agenda instances that process the Jobs and the instances use the MongoDB database to synchronize with each other.

Locking Jobs ✅

Agenda has a lock mechanism to lock the job to one Agenda instance if multiple agenda job processors are running concurrently.

This prevents the same job to be executed multiple times by multiple processes.

Chat API Trusted by world’s biggest corporations | DeadSimpleChat
Chat API and SDk that supports 10 Million Concurrent Users. Features like Pre-Built turn key Chat Solution, Chat API’s, Customization, Moderation, Q&A, Language Translation.
BullMQ (https://github.com/taskforcesh/bullmq)

2. Bull

Bull is a message queue with support for scheduling jobs. It is primarily a message queue but can also be used for scheduling jobs.

Bull is powered by Redis backend, you need to have Redis installed for Bull to work.

Installing Bull is very simple

npm install bull --save

Let's evaluate Bull through all our evaluation criteria.

Support for Scheduling ✳️

Bull Supports Scheduling Jobs, though scheduling jobs in Bull not as clean as Agenda, it does support Job Scheduling by creating a "Delayed" Job.

We cannot specify the time or date as in Agenda, but we can specify the delay in milliseconds.

So if we want to create a Scheduled Job 3 hours from now, we need to specify 3 hours in milliseconds.

Here is an example:

// Bull will by default connect to localhost:6379 redis instance.
const Queue = require('bull');

// Creating our first queue
const sendEmailQueue = new Queue("sendEmailQueue");

// Handing Job
sendEmailQueue.process(async (job) => {
    sendEmailToUser(job.data.email);
});

// Scheduling the Job to Run After 3 hours
const myJob = await myqueue.add({ email: "user@example.com" }, 
                                { delay: 10800000 });

Support for Recurrent Jobs ✅

Bull supports repeatable jobs you can create jobs in bull that are repeated at a specified interval.

In the repeatable job, you can also specify whether the job should run indefinitely or until a specified number of times or until a specified date.

Let's see an example:

const Queue = require("bull");

const processReportQueue = new Queue("processReportQueue");

processReportQueue.process(async (job) => {
    const report = await slowFunctionToComputeReport();
    emailReport(job.data.email, report);
});

const myJob = await processReportQueue.add({
	"email": "user@example.com"
}, {
    // Every day at 3:15 am
    cron: '15 3 * * *'
});

For example, create a job that runs every hour 12 times and then stops.

const myJob = await myqueue.add(
  { foo: 'bar' },
  {
    repeat: {
      every: 3600000, // 1 hr in milliseconds
      limit: 12
    }
  }
);

Support for Persistence ✅

BullMQ Supports Persistence through the Redis Backend. If you schedule a Job in the Job Queue and there is no progress available to handle it the Job will stay in the queue until a processor becomes available and it will be processed by the Job Processor.

The Jobs info is stored in Redis.

Horizontal Scaling ✅

BullMQ has a Redis backend, and horizontal scaling is achieved using the Redis backend. Multiple Job processors can be launched across multiple nodes, and all the  Job processors fetch the new Jobs through the common Redis backend.

Locking Jobs ✅

Bull Supports Locking Jobs, and each Job is assigned to only one Job processor even if there are multiple Job Processors available. This prevents the execution of the same job on multiple job processors.

Chat API Trusted by world’s biggest corporations | DeadSimpleChat
Chat API and SDk that supports 10 Million Concurrent Users. Features like Pre-Built turn key Chat Solution, Chat API’s, Customization, Moderation, Q&A, Language Translation.
Bree (https://github.com/breejs/bree)

3. Bree

Bree is a Node.JS Job Scheduler. It spawns Node.JS worker processes to execute the jobs. Bree supports scheduled and recurrent jobs. Bree doesn't require any backend store like MongoDB or Redis, as it doesn't require any backend store it does not support persistence.

To install bree just run the command

npm install bree

 Bree looks for a folder named jobs/ in the root of the project directory.

├── index.js -- bree start
├── jobs -- jobs directory 
│   ├── interval.js -- a interval job
│   ├── recurrent.js -- a reccurent job
│   └── scheduled.js -- a scheduled job
├── node_modules

Support for Scheduling ✅

To create a scheduled job in Bree first create a directory calledjobs inside your project root, inside the jobs directory create a file for the job, you can name it anything you like, we will call it scheduled.js

Then we will create an index.js file and launch the scheduled job:

const Bree = require('bree');
const moment = require("moment");

const bree = new Bree({
    jobs: [{
        name: "scheduled", // it will run the file named "jobs/scheduled.js"
        date: moment().add(3, 'days').toDate() // accepts javascript date instance
    }]
});

(async () => {
    await bree.start(); // calling the start method is required
})();
index.js
console.log("Scheduled Job Started");

setTimeout(() => {
    console.log("Scheduled job finished");
}, 10000);
scheduled.js

You can also specify a scheduled Job using the following format

const Bree = require('bree');
const moment = require("moment");

const bree = new Bree({
    jobs: [{
        name: "scheduled",
        timeout: '1 min' // this will run the job once after 1 min.
    }]
});

(async () => {
    await bree.start();
})();
index.js

Support Recurrent Jobs ✅

Bree also supports creating recurrent jobs that run at a specified interval. We will create a file named interval.js inside the jobs/ directory. You can name the file anything you want.

In index.js we will specify the name of the job that we have created in the jobs folder that we want to run at a specified interval.

const Bree = require('bree');
const moment = require("moment");

const bree = new Bree({
    jobs: [{
        name: "interval", // it will run the file name "jobs/interval.js"
        interval: 'every 2 days' // it will run the file every 2 days
    }]
});

(async () => {
    await bree.start();
})();
index.js

We can also specify the job in cron format

const Bree = require('bree');
const moment = require("moment");

const bree = new Bree({
    jobs: [{
        name: "interval", // it will run the file name "jobs/interval.js"
        cron: '15 10 ? * *',
    }]
});

(async () => {
    await bree.start();
})();
index.js

Support for Persistence 🚫

As Bree doesn't use any database backend like MongoDB or Redis, it doesn't support persistence. So if you have created a Job to run every 5 minutes and for some reason, the Bree instance crashes after 3 minutes, the Job will not run 2 minutes after the restart but it will run after 5 minutes.

Support for Horizontal Scaling 🚫

Bree does not natively support horizontal scaling, as Bree uses worker threads on the same host to launch the jobs, and there is no backend to synchronise the jobs running across multiple instances.

Locking Jobs 🚫

As Bree runs on the same host and there is no support for launching concurrent job processors technically it doesn't require locking.

Chat API Trusted by world’s biggest corporations | DeadSimpleChat
Chat API and SDk that supports 10 Million Concurrent Users. Features like Pre-Built turn key Chat Solution, Chat API’s, Customization, Moderation, Q&A, Language Translation.

4. Node Cron

Node Cron is a minimalistic job scheduler. It allows the creation of recurring and scheduled jobs using the cron format.

It has a minimal interface and has no support for persistence, horizontal scaling or locking jobs.

Bree launches jobs as separate worker threads, whereas Node Cron runs the jobs in the main thread.

To install node cron run the command:

npm install node-cron

Support for Scheduling ✳️

Node Cron supports scheduled Job using the cron syntax, and if you want to run the job only once then you need to call the stop() method on the job to prevent it from running again.

It is like JavaScript setInterval that accepts Cron syntax instead of milliseconds.

Let's look at an example:

var cron = require('node-cron');

var job = cron.schedule('0 9 2 12', () =>  {
  console.log('Job will at 9:00 AM on 2nd Dec of current year');
  job.stop(); // If task.stop() is not called the task will run every year
});

Support for Recurrent Jobs ✅

Node Cron supports recurring jobs. You need to specify the recurring in Cron Syntax and it will run the job at the specified interval.

var cron = require('node-cron');

cron.schedule('*/2 * * * *', () => {
  console.log('runn the job every two minutes');
});

Support for Persistence 🚫

It is a very minimal job scheduler, and hence it doesn't have any persistence. It does not connect to any data store to store jobs, hence once the Node.JS process is terminated the interval data is lost.

Support for Horizontal Scaling 🚫

As I said it is like JavaScript interval. It has no way of synchronising or scheduling jobs across multiple job processors. It is a simplistic job scheduler.

Locking Jobs 🚫

It does not support locking jobs. As it does not support multiple job processors, hence there is no need to lock the job.

Conclusion

We have looked at 4 popular job schedulers in this blog post, looked at their features and evaluated them using our evaluation criteria. Each Job scheduler has its own strengths, and based on my personal evaluation here is a breakdown of each job scheduler:

  • Agenda: If you need primarily a job scheduler to run jobs at specified dates or at specified intervals and persistence and scalability are important the agenda is the preferred choice.
  • BullMQ: If you need primarily a job queue with support for recurrent jobs and scheduling then BullMQ is a good option. It is an excellent job queue, using with you could dispatch jobs to multiple workers running across multiple hosts and can also use it as a scheduler to run scheduled jobs.
  • Bree: If you need a powerful job scheduler, don't want to run a Redis or MongoDB backend, and don't need scalability across multiple hosts, and persistence is not very important then Bree is a good choice. It also makes use of multiple cores by launching jobs as worker threads but doesn't natively support horizontal scaling across multiple hosts.
  • Node-Cron: If you need a simplistic job scheduler with no frills then you can use node-cron.

These are some relevant articles that are a good read

Add Chat to your Web App with Dead Simple Chat

Dead Simple Chat allows you to easily add prebuilt group chat in minutes to any web or mobile app.

It is highly scalable and can be integrated in minutes. It has Chat SDK, Customization and Moderation features to make the chat fit for any chat use-case, be it Live Streaming, Group Chat or 1-1 Chat.