DEV Community

Cover image for TypeScript Scheduler with node-schedule
Maksym
Maksym

Posted on

TypeScript Scheduler with node-schedule

Here's a TypeScript scheduler using node-schedule library!

// First install the required packages:
// npm install node-schedule @types/node-schedule

import * as schedule from 'node-schedule';
import { Job } from 'node-schedule';

interface TaskConfig {
  name: string;
  schedule: string | Date;
  task: () => Promise<void> | void;
  enabled?: boolean;
}

class TaskScheduler {
  private jobs: Map<string, Job> = new Map();

  /**
   * Schedule a task with cron-like syntax or specific date
   * @param config Task configuration
   * @returns Job instance
   */
  scheduleTask(config: TaskConfig): Job | null {
    if (!config.enabled && config.enabled !== undefined) {
      console.log(`Task ${config.name} is disabled, skipping...`);
      return null;
    }

    try {
      const job = schedule.scheduleJob(config.name, config.schedule, async () => {
        console.log(`[${new Date().toISOString()}] Executing task: ${config.name}`);
        try {
          await config.task();
          console.log(`[${new Date().toISOString()}] Task completed: ${config.name}`);
        } catch (error) {
          console.error(`[${new Date().toISOString()}] Task failed: ${config.name}`, error);
        }
      });

      if (job) {
        this.jobs.set(config.name, job);
        console.log(`Task scheduled: ${config.name} - Next run: ${job.nextInvocation()}`);
      }

      return job;
    } catch (error) {
      console.error(`Failed to schedule task: ${config.name}`, error);
      return null;
    }
  }

  /**
   * Schedule multiple tasks at once
   * @param configs Array of task configurations
   */
  scheduleTasks(configs: TaskConfig[]): void {
    configs.forEach(config => this.scheduleTask(config));
  }

  /**
   * Cancel a specific task
   * @param taskName Name of the task to cancel
   */
  cancelTask(taskName: string): boolean {
    const job = this.jobs.get(taskName);
    if (job) {
      job.cancel();
      this.jobs.delete(taskName);
      console.log(`Task cancelled: ${taskName}`);
      return true;
    }
    console.warn(`Task not found: ${taskName}`);
    return false;
  }

  /**
   * Cancel all scheduled tasks
   */
  cancelAllTasks(): void {
    this.jobs.forEach((job, name) => {
      job.cancel();
      console.log(`Task cancelled: ${name}`);
    });
    this.jobs.clear();
  }

  /**
   * Get information about all scheduled tasks
   */
  getTasksInfo(): Array<{name: string, nextRun: Date | null}> {
    const tasksInfo: Array<{name: string, nextRun: Date | null}> = [];

    this.jobs.forEach((job, name) => {
      tasksInfo.push({
        name,
        nextRun: job.nextInvocation()
      });
    });

    return tasksInfo;
  }

  /**
   * Reschedule an existing task
   * @param taskName Name of the task to reschedule
   * @param newSchedule New schedule (cron or date)
   */
  rescheduleTask(taskName: string, newSchedule: string | Date): boolean {
    const job = this.jobs.get(taskName);
    if (job) {
      const success = job.reschedule(newSchedule);
      if (success) {
        console.log(`Task rescheduled: ${taskName} - Next run: ${job.nextInvocation()}`);
      }
      return success;
    }
    console.warn(`Task not found: ${taskName}`);
    return false;
  }

  /**
   * Graceful shutdown - cancel all jobs
   */
  shutdown(): void {
    console.log('Shutting down scheduler...');
    this.cancelAllTasks();
    schedule.gracefulShutdown();
  }
}
Enter fullscreen mode Exit fullscreen mode

This implementation includes:

  1. Type Safety: Full TypeScript support with interfaces and proper typing
  2. Flexible Scheduling: Supports multiple scheduling formats:
  3. Cron-like syntax ('0 9 * * *' for daily at 9 AM)
  4. Specific dates (new Date())
  5. RecurrenceRule for complex patterns
  6. Task Management:
  7. Schedule single or multiple tasks
  8. Cancel specific tasks or all tasks
  9. Reschedule existing tasks
  10. Enable/disable tasks
  11. Get information about scheduled tasks
  12. Error Handling: Error handling with logging
  13. Graceful Shutdown: Proper cleanup on process termination

Cron Format Quick Reference:

* * * * * *
┬ ┬ ┬ ┬ ┬ ┬
│ │ │ │ │ │
│ │ │ │ │ └ day of week (0 - 7) (0 or 7 is Sun)
│ │ │ │ └───── month (1 - 12)
│ │ │ └────────── day of month (1 - 31)
│ │ └─────────────── hour (0 - 23)
│ └──────────────────── minute (0 - 59)
└───────────────────────── second (0 - 59, optional)
Enter fullscreen mode Exit fullscreen mode

Common Patterns:

'*/5 * * * *' - Every 5 minutes
'0 */2 * * *' - Every 2 hours
'0 9-17 * * 1-5' - Every hour from 9 AM to 5 PM, Monday to Friday
'0 0 1 * *' - First day of every month at midnight

The scheduler handles async tasks, provides detailed logging, and includes graceful shutdown functionality. You can easily extend it with additional features like task persistence, retry logic, or notification systems.

Examples of how to use it


// Example usage
const scheduler = new TaskScheduler();

// Example tasks
const exampleTasks: TaskConfig[] = [
  {
    name: 'daily-report',
    schedule: '0 9 * * *', // Every day at 9:00 AM
    task: async () => {
      console.log('Generating daily report...');
      // Simulate async work
      await new Promise(resolve => setTimeout(resolve, 1000));
      console.log('Daily report generated successfully');
    },
    enabled: true
  },
  {
    name: 'database-cleanup',
    schedule: '0 2 * * 0', // Every Sunday at 2:00 AM
    task: async () => {
      console.log('Starting database cleanup...');
      // Database cleanup logic here
      await new Promise(resolve => setTimeout(resolve, 2000));
      console.log('Database cleanup completed');
    },
    enabled: true
  },
  {
    name: 'health-check',
    schedule: '*/5 * * * *', // Every 5 minutes
    task: () => {
      console.log('Performing health check...');
      // Health check logic here
      console.log('Health check passed');
    },
    enabled: true
  },
  {
    name: 'monthly-summary',
    schedule: '0 0 1 * *', // First day of every month at midnight
    task: async () => {
      console.log('Generating monthly summary...');
      await new Promise(resolve => setTimeout(resolve, 1500));
      console.log('Monthly summary generated');
    },
    enabled: false // Disabled for this example
  },
  {
    name: 'one-time-task',
    schedule: new Date(Date.now() + 10000), // Run once in 10 seconds
    task: () => {
      console.log('Executing one-time task...');
    },
    enabled: true
  }
];

// Schedule all tasks
scheduler.scheduleTasks(exampleTasks);

// Display scheduled tasks info
console.log('\n=== Scheduled Tasks ===');
const tasksInfo = scheduler.getTasksInfo();
tasksInfo.forEach(task => {
  console.log(`${task.name}: Next run at ${task.nextRun}`);
});

// Example of rescheduling a task after 30 seconds
setTimeout(() => {
  console.log('\n=== Rescheduling health-check task ===');
  scheduler.rescheduleTask('health-check', '*/10 * * * *'); // Change to every 10 minutes
}, 30000);

// Example of cancelling a specific task after 1 minute
setTimeout(() => {
  console.log('\n=== Cancelling one-time-task ===');
  scheduler.cancelTask('one-time-task');
}, 60000);

// Graceful shutdown on process termination
process.on('SIGINT', () => {
  console.log('\nReceived SIGINT, shutting down gracefully...');
  scheduler.shutdown();
  process.exit(0);
});

process.on('SIGTERM', () => {
  console.log('\nReceived SIGTERM, shutting down gracefully...');
  scheduler.shutdown();
  process.exit(0);
});

// Advanced scheduling examples:

// Schedule with RecurrenceRule for more complex patterns
const complexRule = new schedule.RecurrenceRule();
complexRule.dayOfWeek = [1, 3, 5]; // Monday, Wednesday, Friday
complexRule.hour = 14; // 2 PM
complexRule.minute = 30; // 30 minutes past the hour

scheduler.scheduleTask({
  name: 'weekday-notification',
  schedule: complexRule,
  task: () => {
    console.log('Weekday notification sent!');
  },
  enabled: true
});

// Schedule for specific dates
const specificDates = [
  new Date(2024, 11, 25, 10, 0, 0), // Christmas Day 2024, 10:00 AM
  new Date(2025, 0, 1, 0, 0, 0),    // New Year 2025, midnight
];

specificDates.forEach((date, index) => {
  scheduler.scheduleTask({
    name: `holiday-task-${index + 1}`,
    schedule: date,
    task: () => {
      console.log(`Holiday task executed at ${date}`);
    },
    enabled: true
  });
});

console.log('\nScheduler is running. Press Ctrl+C to exit.');

// Export the scheduler for use in other modules
export { TaskScheduler, TaskConfig };
Enter fullscreen mode Exit fullscreen mode

This is a small code snippet, hopefully someone find this implementation useful. As always feel free to express your thoughts and criticize this snippet! Cheers

Top comments (0)