Skip to content
Plurafy

Scheduled Tasks and Cron Jobs

Scheduled tasks automate recurring operations. Learn to implement cron jobs, task scheduling, and job orchestration in Node.js applications.

Implement reliable scheduled task execution with proper error handling, monitoring, and distributed coordination.

  • Cron Jobs: Time-based scheduling
  • Interval Tasks: Recurring delays
  • One-time Tasks: Delayed execution
  • Distributed Scheduling: Coordinated multi-node
  • node-cron: Simple cron expressions
  • node-schedule: Human-readable scheduling
  • Bull/BullMQ: Job queue scheduling
  • Agenda: Database-backed scheduling
const cron = require('node-cron');
// Run every day at 2 AM
cron.schedule('0 2 * * *', async () => {
console.log('Running daily backup...');
await performBackup();
});
// Run every Monday at 9 AM
cron.schedule('0 9 * * 1', async () => {
console.log('Weekly report generation...');
await generateWeeklyReport();
});
// Run every 5 minutes
cron.schedule('*/5 * * * *', async () => {
console.log('Checking for pending tasks...');
await processPendingTasks();
});
// Cron pattern: minute hour day-of-month month day-of-week
// Field Allowed values
// Minute 0-59
// Hour 0-23
// Day of month 1-31
// Month 1-12 (or names)
// Day of week 0-7 (0 and 7 are Sunday, or names)
// Examples:
// 0 0 1 * * - 1st day of month at midnight
// 30 23 * * 5 - Friday at 11:30 PM
// 0 * * * * - Every hour
// 0 0 * * * - Every day at midnight
// 0 10-12 * * * - 10 AM, 11 AM, 12 PM every day
const cron = require('node-cron');
const logger = require('./logger');
class ScheduledTask {
constructor(name, cronExpression, handler) {
this.name = name;
this.cronExpression = cronExpression;
this.handler = handler;
this.lastRun = null;
this.lastResult = null;
this.failureCount = 0;
this.maxRetries = 3;
}
start() {
this.task = cron.schedule(this.cronExpression, async () => {
try {
logger.info(`Starting task: ${this.name}`);
const startTime = Date.now();
this.lastResult = await this.handler();
this.lastRun = new Date();
this.failureCount = 0;
const duration = Date.now() - startTime;
logger.info(`Task completed: ${this.name} (${duration}ms)`);
} catch (error) {
this.failureCount++;
logger.error(`Task failed: ${this.name}`, {
error: error.message,
attempt: this.failureCount,
});
if (this.failureCount >= this.maxRetries) {
logger.error(`Task exceeded max retries: ${this.name}`);
// Alert monitoring system
}
}
});
}
stop() {
this.task.stop();
}
getStatus() {
return {
name: this.name,
lastRun: this.lastRun,
lastResult: this.lastResult,
failureCount: this.failureCount,
running: this.task?.status === 'started',
};
}
}
// Usage
const backupTask = new ScheduledTask(
'Database Backup',
'0 2 * * *',
async () => {
return await performBackup();
}
);
backupTask.start();
// Endpoint to check task status
app.get('/admin/scheduled-tasks', (req, res) => {
res.json({
backup: backupTask.getStatus(),
});
});

3. Distributed Task Scheduling with Agenda

Section titled “3. Distributed Task Scheduling with Agenda”
const Agenda = require('agenda');
const agenda = new Agenda({
db: {
address: 'mongodb://localhost:27017/scheduler',
},
});
// Define jobs
agenda.define('send-email-notification', async (job) => {
const { userId, template } = job.attrs.data;
console.log(`Sending ${template} email to user ${userId}`);
await emailService.send(userId, template);
});
agenda.define('cleanup-expired-sessions', async () => {
console.log('Cleaning up expired sessions');
const expiredCount = await database.sessions.deleteExpired();
console.log(`Deleted ${expiredCount} expired sessions`);
});
agenda.define('generate-report', async (job) => {
const { reportType, userId } = job.attrs.data;
console.log(`Generating ${reportType} report for user ${reportType}`);
const report = await generateReport(reportType);
await notificationService.notify(userId, 'Report ready', report);
});
// Start agenda
(async () => {
await agenda.start();
// Schedule recurring jobs
agenda.every('0 2 * * *', 'cleanup-expired-sessions');
// Schedule one-time job
agenda.schedule('in 1 hour', 'send-email-notification', {
userId: 'user-123',
template: 'welcome',
});
// Schedule repeating job with limit
agenda.every('*/5 * * * *', 'check-pending-tasks', {}, {
skipImmediate: true
});
})();
// API to schedule on-demand
app.post('/admin/schedule-report', async (req, res) => {
const job = await agenda.schedule('tomorrow at 9am', 'generate-report', {
reportType: req.body.type,
userId: req.user.id,
});
res.json({ jobId: job.attrs._id });
});

4. Task Queue with Bull and Recurring Jobs

Section titled “4. Task Queue with Bull and Recurring Jobs”
const Queue = require('bull');
const taskQueue = new Queue('scheduled-tasks', {
redis: { host: 'localhost', port: 6379 },
});
class ScheduledTaskManager {
constructor(queue) {
this.queue = queue;
this.tasks = new Map();
}
async registerRecurringTask(name, cronExpression, handler, options = {}) {
const job = await this.queue.add(
{ task: name, handler },
{
repeat: {
cron: cronExpression,
tz: 'America/New_York',
},
jobId: `recurring-${name}`,
...options,
}
);
this.tasks.set(name, job);
console.log(`Registered recurring task: ${name} (${cronExpression})`);
}
processTask(name, handler) {
this.queue.process(name, async (job) => {
try {
const startTime = Date.now();
const result = await handler(job.data);
console.log(`Task ${name} completed in ${Date.now() - startTime}ms`);
return result;
} catch (error) {
console.error(`Task ${name} failed:`, error.message);
throw error;
}
});
}
async cancelTask(name) {
const job = this.tasks.get(name);
if (job) {
await job.remove();
this.tasks.delete(name);
console.log(`Cancelled task: ${name}`);
}
}
}
// Usage
const taskManager = new ScheduledTaskManager(taskQueue);
// Register tasks
taskManager.registerRecurringTask(
'daily-backup',
'0 2 * * *',
async (data) => {
await performBackup();
return { status: 'completed' };
}
);
// Process tasks
taskManager.processTask('daily-backup', async (data) => {
return await performBackup();
});
class RateLimitedScheduler {
constructor(tasksPerHour = 100) {
this.tasksPerHour = tasksPerHour;
this.taskTimes = [];
}
canExecuteTask() {
const now = Date.now();
const oneHourAgo = now - 60 * 60 * 1000;
// Remove old task times
this.taskTimes = this.taskTimes.filter(time => time > oneHourAgo);
return this.taskTimes.length < this.tasksPerHour;
}
async executeTask(handler) {
if (!this.canExecuteTask()) {
const nextSlot = Math.min(...this.taskTimes) + 60 * 60 * 1000;
const waitTime = nextSlot - Date.now();
console.log(`Rate limit reached. Next execution in ${Math.round(waitTime / 1000)}s`);
await this.wait(waitTime);
}
this.taskTimes.push(Date.now());
return await handler();
}
wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// Usage
const scheduler = new RateLimitedScheduler(100); // 100 tasks per hour
cron.schedule('* * * * *', async () => {
await scheduler.executeTask(async () => {
await processTask();
});
});
class ScheduledTaskMonitor {
constructor() {
this.tasks = new Map();
this.alerts = [];
}
registerTask(name, expectedInterval) {
this.tasks.set(name, {
name,
expectedInterval,
lastRun: null,
nextExpectedRun: Date.now() + expectedInterval,
failures: 0,
});
}
recordTaskRun(name) {
const task = this.tasks.get(name);
if (!task) return;
const now = Date.now();
const delay = now - task.nextExpectedRun;
if (delay > 60000) { // More than 1 minute late
this.alert(`Task ${name} is running late (${Math.round(delay / 1000)}s)`);
}
task.lastRun = now;
task.nextExpectedRun = now + task.expectedInterval;
task.failures = 0;
}
recordTaskFailure(name) {
const task = this.tasks.get(name);
if (!task) return;
task.failures++;
if (task.failures >= 3) {
this.alert(`Task ${name} has failed ${task.failures} times`);
}
}
getHealthStatus() {
const now = Date.now();
const tasks = [];
for (const [name, task] of this.tasks) {
const isLate = now > task.nextExpectedRun;
tasks.push({
name,
lastRun: task.lastRun,
isLate,
failures: task.failures,
status: task.failures > 0 ? 'warning' : (isLate ? 'late' : 'healthy'),
});
}
return { tasks, alerts: this.alerts };
}
alert(message) {
console.warn('ALERT:', message);
this.alerts.push({
timestamp: new Date(),
message,
});
// Send to monitoring service
monitoringService.reportAlert({
service: 'scheduler',
message,
severity: 'warning',
});
}
}
// Endpoint for health check
const monitor = new ScheduledTaskMonitor();
monitor.registerTask('daily-backup', 24 * 60 * 60 * 1000);
app.get('/health/scheduler', (req, res) => {
const status = monitor.getHealthStatus();
res.json(status);
});
  1. Reliability

    • Set up monitoring and alerts
    • Implement retry logic
    • Use persistent job storage
    • Track execution history
  2. Timing

    • Account for execution duration
    • Avoid overlapping executions
    • Use appropriate time zones
    • Test scheduling thoroughly
  3. Distribution

    • Prevent duplicate execution on multiple nodes
    • Use database or Redis for coordination
    • Implement leader election
    • Monitor cluster health
  4. Performance

    • Don’t block the event loop
    • Use background workers
    • Implement rate limiting
    • Scale horizontally
  5. Maintenance

    • Document all scheduled tasks
    • Version control task definitions
    • Provide easy enable/disable
    • Log all executions
  • Data Backups: Automated database backups
  • Reports: Scheduled report generation
  • Cleanup: Expired session/cache cleanup
  • Monitoring: Periodic health checks
  • Notifications: Batch email/SMS delivery