DEV Community

Lucas Pereira de Souza
Lucas Pereira de Souza

Posted on

node-cron for scheduled jobs

logotech

# Scheduling Tasks in Node.js: A Comprehensive Guide with Practical Examples

In backend development, we frequently encounter the need to execute tasks at specific times or at regular intervals. Whether it's for sending reminder emails, generating daily reports, cleaning temporary data, or synchronizing information, task automation is crucial for the efficiency and robustness of any application. This post will guide you through the process of installing and using cron libraries in Node.js, demonstrating how to create recurring tasks with practical examples, focusing on a reminder scenario.

Why Automate Tasks?

Automating repetitive tasks frees up valuable resources, both human and computational. Instead of relying on manual intervention, which is prone to errors and time-consuming, we can depend on automated systems to perform actions reliably and punctually. In web applications, this translates to a better user experience, reduced server load, and increased scalability.

Cron Libraries in Node.js: Introduction

The term \"cron\" originates from Unix, where crontab is the standard utility for scheduling commands. In the Node.js ecosystem, several libraries replicate this functionality, offering a user-friendly interface for defining and managing scheduled tasks. Among the most popular are:

  • node-cron: A lightweight and flexible library with a syntax inspired by Unix cron.
  • agenda: A more robust solution that uses MongoDB for job persistence, ideal for production applications requiring reliability.

In this guide, we will focus on the node-cron library due to its simplicity and ease of use for most cases.

Installing node-cron

To get started, ensure you have Node.js and npm (or yarn) installed on your environment. Then, install the node-cron library in your project:

npm install node-cron
# or
yarn add node-cron
Enter fullscreen mode Exit fullscreen mode

If you are using TypeScript, also install the types for the library:

npm install --save-dev @types/node-cron
# or
yarn add --dev @types/node-cron
Enter fullscreen mode Exit fullscreen mode

Creating Recurring Tasks with node-cron

The basic syntax of node-cron allows you to schedule tasks using a cron string, which specifies the execution pattern. The string consists of five fields (or six, depending on configuration, including seconds):

* * * * * *
┬ ┬ ┬ ┬ ┬ ┬
│ │ │ │ │ │
│ │ │ │ │ └─ Day of the Week (0 - 7) (Sunday is 0 or 7)
│ │ │ │ └─ Month (1 - 12)
│ │ │ └─ Day of the Month (1 - 31)
│ │ └─ Hour (0 - 23)
│ └─ Minute (0 - 59)
└─ Second (0 - 59) (optional, defaults to 0 if not included)
Enter fullscreen mode Exit fullscreen mode

Each field can contain:

  • *: Matches any value (e.g., * in minutes means \"every minute\").
  • */n: Executes every n units (e.g., */15 in minutes means \"every 15 minutes\").
  • n: A specific value (e.g., 0 in hours means \"at midnight\").
  • n-m: A range of values (e.g., 9-17 in hours means \"from 9 AM to 5 PM\").
  • n,m,p: A list of values (e.g., 1,3,5 in days of the week means \"Monday, Wednesday, and Friday\").

Example: Task Reminders

Let's create a practical example where we want to schedule reminders for users about pending tasks. Imagine we have a ReminderService that sends notifications.

First, let's define an interface for the task and the service:

// src/interfaces/task.interface.ts

export interface Task {
  id: string;
  userId: string;
  description: string;
  dueDate: Date;
  reminderSent?: boolean;
}

// src/services/reminder.service.ts

import { Task } from '../interfaces/task.interface';

class ReminderService {
  private tasks: Task[] = []; // Simulates an in-memory database

  constructor() {
    // Populating with some example tasks
    this.tasks.push({
      id: 'task-1',
      userId: 'user-123',
      description: 'Finalize monthly report',
      dueDate: new Date(Date.now() + 2 * 60 * 60 * 1000), // In 2 hours
    });
    this.tasks.push({
      id: 'task-2',
      userId: 'user-456',
      description: 'Schedule team meeting',
      dueDate: new Date(Date.now() + 24 * 60 * 60 * 1000), // In 24 hours
    });
    this.tasks.push({
      id: 'task-3',
      userId: 'user-123',
      description: 'Review code for feature X',
      dueDate: new Date(Date.now() + 2 * 24 * 60 * 60 * 1000), // In 2 days
      reminderSent: true, // Reminder already sent
    });
  }

  /**
   * Fetches tasks that need a reminder.
   * @returns Array of tasks needing a reminder.
   */
  getTasksNeedingReminder(): Task[] {
    console.log('Fetching tasks for reminder...');
    return this.tasks.filter(task =>
      !task.reminderSent && // Reminder not yet sent
      task.dueDate > new Date() && // Due date is in the future
      (task.dueDate.getTime() - Date.now()) <= (24 * 60 * 60 * 1000) // Due date is within the next 24 hours
    );
  }

  /**
   * Simulates sending a reminder for a specific task.
   * @param task The task for which the reminder will be sent.
   */
  sendReminder(task: Task): void {
    console.log(`Sending reminder to user ${task.userId} about task \"${task.description}\".`);
    // Here you would implement the actual logic for sending emails, push notifications, etc.
    // Marking the task as reminder sent to avoid duplicate sends
    const taskIndex = this.tasks.findIndex(t => t.id === task.id);
    if (taskIndex !== -1) {
      this.tasks[taskIndex].reminderSent = true;
    }
  }
}

export default new ReminderService();

Enter fullscreen mode Exit fullscreen mode

Now, let's integrate node-cron to trigger the reminder check and sending. We'll create a scheduler.ts script responsible for managing the scheduled tasks.

// src/scheduler.ts

import cron from 'node-cron';
import reminderService from './services/reminder.service';

console.log('Initializing task scheduler...');

// Schedule the task to run every hour, at the 0th minute.
// The pattern '0 * * * *' means: at minute 0 of every hour, every day, every month, every day of the week.
const reminderCronJob = cron.schedule('0 * * * *', () => {
  console.log('Running reminder check job...');

  const tasksToRemind = reminderService.getTasksNeedingReminder();

  if (tasksToRemind.length > 0) {
    console.log(`Found ${tasksToRemind.length} tasks to send reminders for.`);
    tasksToRemind.forEach(task => {
      reminderService.sendReminder(task);
    });
  } else {
    console.log('No tasks found for reminders in this run.');
  }
}, {
  scheduled: true,
  timezone: \"America/Sao_Paulo\" // Set your desired timezone
});

// Optional: Add a listener for job events
reminderCronJob.start(); // Ensure the job starts

reminderCronJob.stop(); // Stop the job (example)

reminderCronJob.setTime(new cron.CronTime('*/5 * * * * *')); // Change the schedule to every 5 seconds (for demonstration only)
reminderCronJob.start(); // Restart with the new schedule

console.log('Task scheduler started. Checking for reminders every hour.');

// To keep the process running (in a real scenario, your Node.js server would already be running)
process.stdin.resume();

process.on('SIGINT', () => {
  console.log('Received SIGINT. Stopping scheduler and exiting...');
  reminderCronJob.stop();
  process.exit(0);
});
Enter fullscreen mode Exit fullscreen mode

Code Explanation:

  1. import cron from 'node-cron';: Imports the node-cron library.
  2. import reminderService from './services/reminder.service';: Imports our service class containing the business logic.
  3. cron.schedule('0 * * * *', () => { ... }, { ... });: This is the main function.
    • The first argument ('0 * * * *') is the cron string that defines when the callback function will be executed (in this case, every hour on the hour).
    • The second argument is the callback function that will be executed at the scheduled time. Inside it, we call getTasksNeedingReminder from our service and iterate over the found tasks to send reminders.
    • The third argument is an options object:
      • scheduled: true: Indicates that the job should start automatically when defined.
      • timezone: \"America/Sao_Paulo\": Essential to ensure that times are interpreted correctly according to your application's timezone.
  4. reminderCronJob.start();: Starts the execution of the scheduled job.
  5. reminderCronJob.stop();: Allows you to stop the job's execution.
  6. reminderCronJob.setTime(...): Demonstrates how you can change the schedule of an existing job.
  7. process.stdin.resume();: In a simple script like this, we use this to keep the Node.js process running, as cron.schedule runs in the background. In a real web server (like Express), the server itself keeps the process active.
  8. process.on('SIGINT', ...): Basic handling for the interrupt signal (Ctrl+C), ensuring the job is stopped gracefully before the process exits.

To run this script, save it as scheduler.ts, compile it to JavaScript (if using TypeScript), and run it with Node.js:

tsc # If using TypeScript
node dist/scheduler.js # Or the path to your compiled JS file
Enter fullscreen mode Exit fullscreen mode

You will see logs indicating when the reminder check job runs and when reminders are sent.

Best Practices and Considerations

  • Timezone: Always set the timezone correctly in cron.schedule to avoid surprises with execution times.
  • State Management: In robust applications, the state of jobs (like \"reminder sent") should be persisted in a database, not in memory, to ensure tasks are not lost if the server restarts.
  • Error Handling: Implement robust try...catch blocks within the job callback functions to catch and log any errors that may occur during execution, preventing a failed job from interrupting others.
  • Concurrency: If a task takes longer to execute than the interval between runs, you might encounter concurrency issues. Libraries like agenda offer mechanisms to handle this. For node-cron, you can implement your own locking logic.
  • Schedule Complexity: Avoid overly complex cron strings. If the scheduling logic is too intricate, consider alternative approaches or simplify the schedule and add additional logic within the callback.
  • Monitoring: Monitor the execution of your scheduled jobs. Logging and monitoring tools are essential to ensure everything is working as expected.

Conclusion

Scheduling recurring tasks in Node.js is a powerful technique for automating processes and improving the efficiency of your applications. With libraries like node-cron, the process becomes accessible and flexible. By understanding cron syntax, implementing business logic in an organized manner, and following best practices, you can create robust systems that reliably execute important actions, from sending reminders to maintaining complex routines. Remember to adapt the examples and considerations to the specific complexity and requirements of your project.

Top comments (0)