Job Scheduling in Node.js with Node-cron
Node-cron is a popular Node.js library that simplifies scheduling background tasks in your applications. It excels at automating repetitive jobs such as sending emails, processing images, backing up databases, and more.
Despite its lightweight nature, Node-cron offers robust features, including support for cron syntax and the ability to validate cron expressions. This makes scheduling tasks straightforward, especially if you are already familiar with cron, a tool available on Unix-like operating systems.
This article will explore Node-cron's functionalities, from fundamental aspects to advanced techniques. Additionally, you will learn how to monitor cron jobs to ensure they operate reliably.
Let's jump right in!
Prerequisites
Before starting this tutorial, ensure you have:
- The latest version of Node.js installed.
- Basic knowledge of writing applications using Node.js.
Setting up the project directory
In this section, you will download a repository that includes some pre-written code to help you get started with scheduling tasks using Node-cron.
To begin, clone the remote repository with the following command:
Next, move into the directory:
Now, use npm to install all the necessary dependencies in the package.json
file. This includes Node-cron for scheduling tasks and SQLite for database
management:
With all the dependencies installed, you can start scheduling tasks in the next section.
Scheduling tasks with Node-cron
In this section, you will schedule a basic task with Node-cron.
To do that, open the index.js file in your favourite editor. This tutorial
assumes you are using VS Code, and you can open the file with the code command
like this:
In this code, you define an exportData() function that logs "Exporting Sales
report 1 data..." to the console.
To schedule this function, you use cron.schedule() with a cron expression
('*/5 * * * * *') to run the exportData() function every 5 seconds.
Save your file and run it with the following command:
When it runs for a few seconds, you will see output like this:
The output confirms that the scheduler is active and successfully running the
exportData() function at the specified interval.
Customizing Node-cron jobs
Often, you might need to customize jobs and pass custom data to them. For example, a job that processes multiple files might require a different file path each time, or a job sending emails might need different recipient names and addresses for each execution. This ensures tasks are executed with the correct context and parameters.
To achieve this, follow these steps:
First, remove the highlighted lines from the existing code:
Next, modify the code to pass custom data to the scheduler method:
With this change, the exportData() function now accepts data as a parameter,
destructures name and path from data, and logs a message indicating the
export operation with these values. In the cron.schedule() method, you call
exportData with custom data specifying a sales report's name and
file path.
Run the command with the following:
The output will look like the following:
The main advantage of this approach is the flexibility to pass different data to each job, which allows the process to handle multiple files in a directory. Consider the following:
In this code, you initialize the dataArray containing three objects, each
representing a sales report with a name and file path. You also declare a
variable dataIndex to keep track of the current item being processed from
dataArray when exporting data periodically using node-cron. This allows it
to cycle through multiple jobs.
With that, save your file and run the program:
You will see that it now runs the export function with different reports:
This demonstrates how you can use Node-cron to handle multiple files effectively.
Using cron expressions to schedule jobs
Cron expressions are a core part of the Node-cron package, providing flexibility and precision in scheduling tasks. This section will dive into cron expressions to help you use them efficiently.
A cron expression typically consists of five fields representing the minute, hour, day of the month, month, and day of the week:
For quick reference, here are the values each field can take:
| Field | Allowed Values |
|---|---|
| Minute | 0-59 |
| Hour | 0-23 |
| Day of the Month | 1-31 |
| Month | 1-12 or JAN-DEC |
| Day of the Week | 0-6 or SUN-SAT |
Here are some examples using cron expressions:
You can specify multiple values separated by commas to define exact times for the task to run. This is useful for specific scheduling needs within an hour or day:
You can also define a range of values to specify a continuous interval within a field, which is helpful for tasks that need to run frequently within a specific period:
Step values can be used with ranges, following a range with a '/' and a number.
For example, 1-10/2 is the same as 1,3,5,7,9. Steps can also be used after
an asterisk, allowing you to schedule tasks at regular intervals.
For the month and weekday fields, you can also use full names. This makes the cron expressions more readable and easier to understand at a glance:
Short names for months and days of the week are also supported, providing a more compact way to define cron schedules:
Validating cron expressions
Given how frequently you must use cron expressions, ensuring they are valid is
essential to avoid any unexpected issues. The Node-cron package includes a
validate() function to help with this.
Here's an example demonstrating how to use the validate() function:
Run the file with the following command:
The output confirms that the validation function correctly identifies
10 * * * * * as a valid cron expression and 60 * * * * * as invalid,
demonstrating the functionality of the validation process.
Using online cron expression validators
If cron expressions still seem complex, online tools are available to help validate and understand them. You can input your cron expressions, and these tools will provide a human-readable interpretation.
A popular tool for checking and understanding cron expressions is Crontab Guru. It provides an intuitive interface that translates cron expressions into human-readable formats.
In the screenshot, the tool translates the expression 5 4 * * * to "At 04:05."
This means the task is scheduled to run at 04:05 AM every day.
Managing tasks with Node-cron
Node-cron provides several methods for managing your tasks. You can start them later or stop them when they meet certain conditions.
Take the following example:
Upon running the code, you will see an output similar to this:
The script exits without starting the task because of the option
{ scheduled: false } passed to the cron.schedule() method. This behaviour
can be beneficial when you need to defer the start of a cron job until specific
runtime conditions are met.
Starting a deferred cron job
To start the deferred cron job, you can use the start() method like this:
In the code snippet, you manually start a task and log a message to the console indicating that the scheduler has started.
Upon running the file, you will see the scheduler start and perform its task, with output similar to:
This demonstrates that the scheduler begins executing its job immediately after being manually started.
Stopping a running cron job
While running cron jobs, you might need to stop a job under certain conditions,
such as not wanting it to run for more than an hour. To do this, you can stop
the job anytime with the stop() method.
For example, you can stop the task after 20 seconds like this:
Now, when you run the file, you will see that it stops after 20 seconds:
This allows you to manage tasks effectively by starting and stopping them based on your requirements.
Scheduling database backups with Node-cron
In this section, you'll apply what you have learned about Node-cron to back up an SQLite database into archives.
Open the backup.js file in your project directory:
It will contain the following code:
This script uses the spawn() function from the child_process module to run
sqlite3, creating backups of an SQLite database named movies.db. It
dynamically generates a backup filename using the current timestamp, formatted
with date-fns.
The backupDatabase() function manages the backup process, logging success or
failure messages. It includes error handling for potential issues, such as
failure to start the backup or unexpected termination.
To run the script, execute the following command:
Upon running, you will see the following output:
The output shows that the database backup process started successfully, creating
a backup file named backup-2024-06-24_15-10-48.sqlite.gz.
Next, list the contents of the directory:
The directory will now contain the newly created archive:
Currently, the script requires manual execution each time you want to create a backup. To automate this process, you can use Node-cron to schedule the task to run at regular intervals.
Add the highlighted code below to automate the backups:
Save your file and run the program:
Upon running, you will see the following output:
After running for a few minutes, list the directory contents:
You will see multiple archives confirming that the backups are working as intended:
You can now schedule backup tasks to run repeatedly, ensuring your database is backed up regularly. In the next section, you will monitor the scheduled jobs.
Monitoring scheduled jobs in production
When you set up automated tasks, it's crucial to establish a system that monitors these jobs and notifies you of any issues. Failure to do so can result in unnoticed job failures. A notable example is GitLab's data loss due to a failed backup process, as detailed in their post-mortem. This incident highlights the importance of monitoring scheduled tasks to be aware of failures and resolutions.
Better Stack is an excellent tool for this purpose. It actively monitors your jobs and sends alerts through various channels, such as emails, phone calls, texts, and Slack, if there are any issues. This ensures you stay informed about your job status and can address problems promptly.
To set this up, register for a free Better Stack account and locate the Heartbeats section on the sidebar. Click the Create Heartbeat button:
Next, provide your preferred name for the monitor and specify how frequently the job executes. In the On-call escalation section, choose how you want to be notified—in this case, via email. When done, click Create Heartbeat:
You will then be redirected to the Heartbeat page, which provides a new endpoint to monitor the jobs:
Copy the endpoint from Better Stack and return to your text editor. Create a
.env file and add the following, replacing <your_heartbeat_url> with the
endpoint:
Next, install the dotenv package to manage environment variables:
Now update the backup.js file to use fetch and send a request to the Heartbeat
endpoint every time the job executes:
Save your file and run the program:
When the first backup runs successfully, Better Stack will confirm that it is "UP," indicating that it is running:
To ensure the monitoring works even when the script fails, stop the script and wait for a few minutes. When this happens, the application won't ping the Better Stack endpoint anymore. Consequently, Better Stack will mark the task as "Down" and update the status:
It will then send an alert to your preferred channels:
With this, you can be confident that your scheduled tasks are running smoothly and that you'll be alerted if something goes wrong. Logging application failures can also help you review and resolve issues. See our Node.js logging guide for more details.
Limitations of Node-cron
So far, you have explored Node-cron and its features and learned how to use it to schedule tasks. The tool has many advantages, the biggest being that it is lightweight and easy to learn. If you know the cron syntax, you can quickly start creating schedules.
However, Node-cron has limitations when it comes to complex scheduling needs. The most significant drawback is its lack of a database system to store or manage jobs. On the other hand, schedulers like Agenda and BullMQ offer the benefit of databases that store jobs, empowering you to resume and continue running tasks even after a crash. In contrast, Node-cron loses all progress if it crashes.
Additionally, Node-cron lacks features like a human-readable syntax that other tools support. While cron syntax is popular, it can be difficult for users who are unfamiliar with it to learn and use. Other features missing from Node-cron include job prioritization and comprehensive management tools.
Final thoughts
In this tutorial, you explored some of the features of Node-cron and used it to schedule tasks in Node.js. You also implemented a monitoring system with Better Stack to notify you if the tasks are running smoothly or encountering issues.
To explore the tool further, check out the documentation for Node-cron and Better Stack.
Thanks for reading, and happy scheduling!