100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Node.js & Express Backend
25 minintermediate

Cron Jobs and Scheduled Tasks

Cron Jobs and Scheduled Tasks

Not all backend work is triggered by HTTP requests. Some tasks run on a schedule: sending weekly digest emails, cleaning up expired sessions, generating daily reports, archiving old data, or checking the health of external services. In Node.js, scheduled tasks are implemented using cron-style scheduling libraries or BullMQ's repeatable jobs. The two most common libraries are node-cron (simple, in-process scheduling) and node-schedule (more flexible, RRULE support).

Analogy🏏Cricket
Think of it like cricket: The groundskeeper at a cricket ground doesn't wait for someone to call and say 'please mow the pitch.' He follows a fixed schedule: mow Monday and Thursday, roll Tuesday, cover Friday evening, remove covers Saturday morning. These scheduled maintenance tasks keep the ground match-ready without anyone having to remember to ask. Your cron jobs are the groundskeeper's schedule — automatic, predictable maintenance of your application's data and infrastructure.

node-cron: In-Process Scheduling

node-cron runs scheduled functions within the same Node.js process. It uses a standard cron expression: minute hour day-of-month month day-of-week. The job function executes at the scheduled time. This is simple to set up but has one limitation: if the process restarts mid-job, the job is lost. Use it for lightweight tasks where occasional misses are acceptable.

bash
npm install node-cron
javascript
const cron = require('node-cron');

// Cron expression: second(optional) minute hour day month weekday
// '*' = every unit, '0' = at zero, '*/5' = every 5 units
// Runs every day at midnight
cron.schedule('0 0 * * *', async () => {
  console.log('[CRON] Running midnight cleanup:', new Date().toISOString());
  try {
    const result = await Session.deleteMany({ expiresAt: { $lt: new Date() } });
    console.log('[CRON] Deleted', result.deletedCount, 'expired sessions');
  } catch (err) {
    console.error('[CRON] Cleanup failed:', err.message);
  }
}, {
  timezone: 'Asia/Kolkata'   // always specify timezone
});

// Runs every Monday at 9 AM IST
cron.schedule('0 9 * * 1', async () => {
  const report = await generateWeeklySalesReport();
  await sendReportEmail({ to: '[email protected]', report });
});

// Runs every 5 minutes (health check)
cron.schedule('*/5 * * * *', async () => {
  await checkExternalApiHealth();
});
Analogy🏏Cricket
Think of it like cricket: cron.schedule() is the match timetable. '0 9 * * 1' is Monday 9 AM — just like 'match starts Monday 9 AM.' The groundskeeper (Node.js event loop) checks the timetable, and when the time matches, executes the task. '*/5 * * * *' is like an umpire checking the light meter every five overs.

BullMQ Repeatable Jobs for Durable Scheduling

For production applications where every scheduled job execution must be guaranteed, use BullMQ repeatable jobs. Unlike node-cron, BullMQ stores the next run time in Redis. If your server restarts or crashes, the job will still run when the server comes back up because Redis remembers when it was due.

Analogy🏏Cricket
🏏 Think of it like cricket: node-cron is like a coach who keeps the training timetable only in his own head — if he goes home sick, that morning's session simply never happens and no one remembers it was due. BullMQ repeatable jobs instead write the next scheduled start into Redis, the way a match's start time is recorded in the official fixtures register held at the ground, not in one person's memory. So when your server restarts or crashes and comes back — like a groundstaff shift changing over — Redis still holds the appointment and the job fires the moment the server is up, just as an interrupted fixture resumes at its recorded scheduled time because the register never forgot it. The payoff: for scheduled work where every single run must be guaranteed, durable Redis-backed scheduling survives restarts and crashes that would make an in-memory scheduler silently skip a run.
javascript
const { Queue, Worker } = require('bullmq');
const connection = { host: process.env.REDIS_HOST || 'localhost', port: 6379 };

const scheduledQueue = new Queue('scheduled', { connection });

// Add once; BullMQ manages recurring scheduling in Redis
async function registerScheduledJobs() {
  // Remove old definitions to avoid duplicates on restart
  await scheduledQueue.removeRepeatableByKey('weekly-report');
  await scheduledQueue.removeRepeatableByKey('daily-cleanup');

  // Weekly report: every Monday 9 AM IST (UTC+5:30 = 03:30 UTC)
  await scheduledQueue.add('weekly-report', {}, {
    repeat: { pattern: '30 3 * * 1' }
  });

  // Daily cleanup: every day at midnight IST (18:30 UTC)
  await scheduledQueue.add('daily-cleanup', {}, {
    repeat: { pattern: '30 18 * * *' }
  });

  console.log('[Scheduler] Scheduled jobs registered');
}

// Call this once when your app starts
registerScheduledJobs();

How Cron Expressions Work

A cron expression has five (or six) fields: minute (0-59), hour (0-23), day-of-month (1-31), month (1-12), day-of-week (0-7, where 0 and 7 are Sunday). Special characters: * (any), , (list), - (range), / (step). Examples: 0 2 * * * is daily at 2 AM; 0 */6 * * * is every 6 hours; 0 9 1 * * is the 1st of every month at 9 AM; 0 9 * * 1-5 is weekdays at 9 AM.

Analogy🏏Cricket
🏏 Think of it like cricket: a cron expression is a five-field bowling schedule, read left to right like the columns of a fixtures grid — minute, hour, day-of-month, month, day-of-week (where 0 and 7 both mean Sunday, the way a rest day can be listed two ways). The special characters are your scheduling shorthand: an asterisk means 'any' (bowl in every over), a comma lists specific slots, a hyphen gives a range like overs 1 through 5, and a slash sets a step like 'every sixth over'. So '0 2 * * *' is a nightly 2 AM roller of the pitch, '0 */6 * * *' is groundskeeping every six hours, '0 9 1 * *' is a first-of-the-month inspection, and '0 9 * * 1-5' is a weekday-only 9 AM session skipping the weekend. The payoff: read the five fields in order and you can dictate a precise recurring schedule — daily, hourly, weekday-only — as exactly as a coach fixing which overs get bowled.
javascript
// Common cron patterns
const SCHEDULES = {
  EVERY_MINUTE:      '* * * * *',
  EVERY_5_MINUTES:   '*/5 * * * *',
  EVERY_HOUR:        '0 * * * *',
  DAILY_MIDNIGHT:    '0 0 * * *',
  DAILY_9AM:         '0 9 * * *',
  WEEKDAYS_9AM:      '0 9 * * 1-5',
  MONDAYS_9AM:       '0 9 * * 1',
  FIRST_OF_MONTH:    '0 0 1 * *',
  EVERY_SIX_HOURS:   '0 */6 * * *'
};

// Validate a cron expression before using it
const cron = require('node-cron');
function isValidCron(expression) {
  return cron.validate(expression);
}

console.log(isValidCron('0 9 * * 1'));   // true
console.log(isValidCron('99 9 * * 1'));  // false (minute 99 invalid)

Always specify the timezone option in node-cron. Without it, the schedule runs in the server's system timezone, which may differ between development and production environments — causing jobs to fire at unexpected times.

Lesson 29 of 36
0% complete