Cron Jobs: Schedule Tasks in Node.js
Learn: Cron Jobs: Schedule Tasks in Node.js
Welcome to TopperBlog! 👋
I'm a tech content creator passionate about helping developers level up their careers and master cutting-edge technologies.
🎯 What I Write About:
• AI/ML Engineering & LLMs
• Web3 & Blockchain Development
• System Design & Architecture
• Interview Preparation (FAANG)
• Freelancing & Remote Work
• Modern Tech Stacks (Next.js, React, Rust, TypeScript)
• Performance Optimization & Best Practices
💼 Mission: Sharing practical, actionable insights that accelerate your tech career and maximize your earning potential.
📚 15+ In-Depth Guides covering everything from earning $10k/month as a freelancer to cracking FAANG interviews.
🌐 Let's connect and grow together in this amazing tech journey!
#TechBlogger #SoftwareEngineering #CareerGrowth #WebDevelopment #AIEngineering
Cron Jobs: Schedule Tasks in Node.js
Problem
Applications often need to execute tasks automatically at specific times or intervals without manual intervention. Common scenarios include:
- Sending periodic email notifications
- Cleaning up temporary files or database records
- Generating reports at scheduled times
- Syncing data with external APIs
- Running maintenance tasks during off-peak hours
- Processing batch jobs overnight
Without a proper scheduling mechanism, developers resort to inefficient workarounds like infinite loops with delays, leading to poor resource management and unreliable execution.
Solution
Node.js provides several approaches to handle scheduled task execution:
- Native
setInterval()/setTimeout()- Simple but limited, doesn't persist across restarts - Cron expression libraries - Parse and execute tasks based on cron syntax
- Job queue systems - Distributed task scheduling with persistence (Bull, RabbitMQ)
- Dedicated cron packages - Purpose-built solutions like
node-cronornode-schedule
The optimal solution combines a reliable cron library with proper error handling, logging, and graceful shutdown mechanisms.
Code Implementation
1. Basic Setup with node-cron
// npm install node-cron
const cron = require('node-cron');
// Schedule a task to run every minute
cron.schedule('* * * * *', () => {
console.log('Running a task every minute');
});
// Schedule a task to run at 2:30 AM every day
cron.schedule('30 2 * * *', () => {
console.log('Running daily maintenance at 2:30 AM');
});
// Cron expression format: minute hour day month day-of-week
// * * * * * (every minute)
// 0 * * * * (every hour)
// 0 0 * * * (daily at midnight)
// 0 0 * * 0 (weekly on Sunday)
// 0 0 1 * * (monthly on the 1st)
2. Production-Ready Implementation
// cronJobs.js
const cron = require('node-cron');
const logger = require('./logger'); // Your logging utility
class CronJobManager {
constructor() {
this.jobs = [];
}
// Schedule a job with error handling
scheduleJob(name, cronExpression, taskFunction) {
try {
const job = cron.schedule(cronExpression, async () => {
const startTime = Date.now();
try {
logger.info(`[${name}] Job started`);
await taskFunction();
const duration = Date.now() - startTime;
logger.info(`[${name}] Job completed in ${duration}ms`);
} catch (error) {
logger.error(`[${name}] Job failed:`, error);
// Send alert notification
await this.notifyFailure(name, error);
}
});
this.jobs.push({ name, job });
logger.info(`[${name}] Cron job scheduled: ${cronExpression}`);
return job;
} catch (error) {
logger.error(`Failed to schedule job ${name}:`, error);
throw error;
}
}
// Stop a specific job
stopJob(name) {
const jobIndex = this.jobs.findIndex(j => j.name === name);
if (jobIndex !== -1) {
this.jobs[jobIndex].job.stop();
this.jobs.splice(jobIndex, 1);
logger.info(`[${name}] Job stopped`);
}
}
// Stop all jobs
stopAllJobs() {
this.jobs.forEach(({ name, job }) => {
job.stop();
logger.info(`[${name}] Job stopped`);
});
this.jobs = [];
}
// Notify on job failure
async notifyFailure(jobName, error) {
// Implement your notification logic (email, Slack, etc.)
logger.error(`Alert: Job ${jobName} failed - ${error.message}`);
}
}
module.exports = new CronJobManager();
3. Task Definitions
// tasks/emailNotifications.js
const db = require('../database');
const emailService = require('../services/emailService');
async function sendDailyDigest() {
const users = await db.query('SELECT * FROM users WHERE digest_enabled = true');
for (const user of users) {
const digest = await generateDigest(user.id);
await emailService.send({
to: user.email,
subject: 'Your Daily Digest',
html: digest
});
}
}
async function generateDigest(userId) {
const activities = await db.query(
'SELECT * FROM activities WHERE user_id = ? AND created_at > NOW() - INTERVAL 1 DAY',
[userId]
);
return `<h1>Your Daily Summary</h1><p>${activities.length} activities</p>`;
}
module.exports = { sendDailyDigest };
// tasks/databaseCleanup.js
const db = require('../database');
async function cleanupExpiredSessions() {
const result = await db.query(
'DELETE FROM sessions WHERE expires_at < NOW()'
);
console.log(`Cleaned up ${result.affectedRows} expired sessions`);
}
async function archiveOldLogs() {
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
await db.query(
'INSERT INTO logs_archive SELECT * FROM logs WHERE created_at < ?',
[thirtyDaysAgo]
);
await db.query(
'DELETE FROM logs WHERE created_at < ?',
[thirtyDaysAgo]
);
}
module.exports = { cleanupExpiredSessions, archiveOldLogs };
// tasks/dataSync.js
const axios = require('axios');
const db = require('../database');
async function syncExternalData() {
try {
const response = await axios.get('https://api.example.com/data');
const data = response.data;
for (const item of data) {
await db.query(
'INSERT INTO external_data (id, content, synced_at) VALUES (?, ?, NOW()) ON DUPLICATE KEY UPDATE content = ?, synced_at = NOW()',
[item.id, JSON.stringify(item), JSON.stringify(item)]
);
}
console.log(`Synced ${data.length} items`);
} catch (error) {
console.error('Data sync failed:', error.message);
throw error;
}
}
module.exports = { syncExternalData };
4. Application Initialization
// app.js
const express = require('express');
const cronJobManager = require('./cronJobs');
const { sendDailyDigest } = require('./tasks/emailNotifications');
const { cleanupExpiredSessions, archiveOldLogs } = require('./tasks/databaseCleanup');
const { syncExternalData } = require('./tasks/dataSync');
const app = express();
// Initialize cron jobs
function initializeCronJobs() {
// Send daily digest at 8 AM
cronJobManager.scheduleJob(
'daily-digest',
'0 8 * * *',
sendDailyDigest
);
// Clean up sessions every hour
cronJobManager.scheduleJob(
'cleanup-sessions',
'0 * * * *',
cleanupExpiredSessions
);
// Archive logs daily at 3 AM
cronJobManager.scheduleJob(
'archive-logs',
'0 3 * * *',
archiveOldLogs
);
// Sync external data every 30 minutes
cronJobManager.scheduleJob(
'sync-data',
'*/30 * * * *',
syncExternalData
);
}
// Graceful shutdown
process.on('SIGTERM', () => {
console.log('SIGTERM received, stopping cron jobs...');
cronJobManager.stopAllJobs();
process.exit(0);
});
// Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
initializeCronJobs();
});
module.exports = app;
5. Advanced: Job Validation & Monitoring
// cronJobs.js (enhanced)
const cron = require('node-cron');
const EventEmitter = require('events');
class CronJobManager extends EventEmitter {
constructor() {
super();
this.jobs = [];
this.metrics = {};
}
scheduleJob(name, cronExpression, taskFunction, options = {}) {
// Validate cron expression
if (!cron.validate(cronExpression)) {
throw new Error(`Invalid cron expression: ${cronExpression}`);
}
const { maxDuration = 3600000, retries = 0 } = options;
this.metrics[name] = { runs: 0, failures: 0, lastRun: null };
const job = cron.schedule(cronExpression, async () => {
let attempt = 0;
while (attempt <= retries) {
try {
const startTime = Date.now();
// Execute with timeout
const result = await Promise.race([
taskFunction(),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Job timeout')), maxDuration)
)
]);
const duration = Date.now() - startTime;
this.metrics[name].runs++;
this.metrics[name].lastRun = new Date();
this.emit('job-success', { name, duration, result });
break;
} catch (error) {
attempt++;
if (attempt > retries) {
this.metrics[name].failures++;
this.emit('job-failure', { name, error, attempt });
}
}
}
});
this.jobs.push({ name, job });
return job;
}
getMetrics(name) {
return this.metrics[name] || null;
}
getAllMetrics() {
return this.metrics;
}
}
module.exports = new CronJobManager();
6. Testing Cron Jobs
// __tests__/cronJobs.test.js
const cronJobManager = require('../cronJobs');
describe('Cron Jobs', () => {
afterEach(() => {
cronJobManager.stopAllJobs();
});
test('should execute job at scheduled time', (done) => {
const mockTask = jest.fn();
cronJobManager.scheduleJob('test-job', '*/1 * * * * *', mockTask);
setTimeout(() => {
expect(mockTask).toHaveBeenCalled();
done();
}, 1100);
});
test('should handle job failures gracefully', (done) => {
const failingTask = jest.fn().mockRejectedValue(new Error('Task failed'));
cronJobManager.on('job-failure', ({ name, error }) => {
expect(name).toBe('failing-job');
expect(error.message).toBe('Task failed');
done();
});
cronJobManager.scheduleJob('failing-job', '*/1 * * * * *', failingTask);
});
test('should validate cron expressions', () => {
expect(() => {
cronJobManager.scheduleJob('invalid', 'invalid-expression', () => {});
}).toThrow('Invalid cron expression');
});
});
Key Takeaways
- Use
node-cronfor simple, reliable cron scheduling - Implement error handling with retry logic and notifications
- Add logging and metrics for monitoring job health
- Handle graceful shutdown to prevent orphaned processes
- Validate cron expressions before scheduling
- Set timeouts to prevent hanging jobs
- Consider distributed systems (Bull, RabbitMQ) for scalability
This approach ensures reliable, maintainable background job execution in production Node.js applications.