# Cron Jobs & Task Scheduling in Node.js **Meta-Description:** Task Scheduling für Node.js. Cron Syntax, node-cron, BullMQ Repeatable Jobs und Serverless Scheduling. **Keywords:** Cron Jobs, Task Scheduling, Node.js, node-cron, BullMQ, Scheduled Tasks, Background Jobs --- ## Einführung Automatisierte Aufgaben sind das Rückgrat jeder Anwendung: **Backups, Reports, Cleanup, Sync-Jobs**. Mit Cron Jobs laufen sie zuverlässig zu definierten Zeiten – ohne manuellen Eingriff. --- ## Cron Syntax ``` ┌─────────────────────────────────────────────────────────────┐ │ CRON EXPRESSION │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌───────────── Minute (0-59) │ │ │ ┌───────────── Stunde (0-23) │ │ │ │ ┌───────────── Tag des Monats (1-31) │ │ │ │ │ ┌───────────── Monat (1-12) │ │ │ │ │ │ ┌───────────── Wochentag (0-7, 0 und 7 = Sonntag)│ │ │ │ │ │ │ │ │ * * * * * │ │ │ │ Beispiele: │ │ ├── * * * * * → Jede Minute │ │ ├── 0 * * * * → Jede volle Stunde │ │ ├── 0 0 * * * → Täglich um Mitternacht │ │ ├── 0 8 * * 1-5 → Mo-Fr um 8:00 │ │ ├── 0 0 1 * * → Am 1. jeden Monats │ │ ├── */15 * * * * → Alle 15 Minuten │ │ └── 0 9,18 * * * → Um 9:00 und 18:00 │ │ │ └─────────────────────────────────────────────────────────────┘ ``` --- ## node-cron Setup ```bash npm install node-cron npm install -D @types/node-cron ``` ```typescript import cron from 'node-cron'; // Basic Scheduled Task cron.schedule('* * * * *', () => { console.log('Runs every minute'); }); // Mit Timezone cron.schedule('0 8 * * *', () => { console.log('Daily at 8:00 AM Berlin time'); }, { timezone: 'Europe/Berlin' }); // Task mit Start/Stop const task = cron.schedule('*/5 * * * *', () => { console.log('Every 5 minutes'); }, { scheduled: false // Nicht automatisch starten }); // Manuell starten/stoppen task.start(); // task.stop(); // Validation const isValid = cron.validate('* * * * *'); // true ``` --- ## Praktische Scheduler-Klasse ```typescript import cron, { ScheduledTask } from 'node-cron'; interface Job { name: string; schedule: string; handler: () => Promise; enabled: boolean; timezone?: string; } class TaskScheduler { private tasks: Map = new Map(); private jobs: Map = new Map(); register(job: Job) { this.jobs.set(job.name, job); if (job.enabled) { this.startJob(job.name); } } startJob(name: string) { const job = this.jobs.get(name); if (!job) throw new Error(`Job ${name} not found`); if (this.tasks.has(name)) { console.log(`Job ${name} already running`); return; } const task = cron.schedule(job.schedule, async () => { const startTime = Date.now(); console.log(`[${name}] Starting...`); try { await job.handler(); console.log(`[${name}] Completed in ${Date.now() - startTime}ms`); } catch (error) { console.error(`[${name}] Failed:`, error); // Alert senden } }, { timezone: job.timezone || 'Europe/Berlin' }); this.tasks.set(name, task); console.log(`[${name}] Scheduled: ${job.schedule}`); } stopJob(name: string) { const task = this.tasks.get(name); if (task) { task.stop(); this.tasks.delete(name); console.log(`[${name}] Stopped`); } } stopAll() { for (const [name, task] of this.tasks) { task.stop(); console.log(`[${name}] Stopped`); } this.tasks.clear(); } getStatus(): Array<{ name: string; schedule: string; running: boolean }> { return Array.from(this.jobs.values()).map(job => ({ name: job.name, schedule: job.schedule, running: this.tasks.has(job.name) })); } } // Verwendung const scheduler = new TaskScheduler(); scheduler.register({ name: 'daily-backup', schedule: '0 2 * * *', handler: async () => { await performDatabaseBackup(); }, enabled: true }); scheduler.register({ name: 'hourly-sync', schedule: '0 * * * *', handler: async () => { await syncExternalData(); }, enabled: true }); scheduler.register({ name: 'weekly-report', schedule: '0 9 * * 1', // Montag 9:00 handler: async () => { await generateWeeklyReport(); await sendReportEmail(); }, enabled: true }); // Graceful Shutdown process.on('SIGTERM', () => { scheduler.stopAll(); process.exit(0); }); ``` --- ## BullMQ Repeatable Jobs ```typescript import { Queue, Worker } from 'bullmq'; import IORedis from 'ioredis'; const connection = new IORedis(process.env.REDIS_URL!); // Queue für Scheduled Jobs const scheduledQueue = new Queue('scheduled-tasks', { connection }); // Repeatable Jobs registrieren async function setupScheduledJobs() { // Täglich um 2:00 UTC await scheduledQueue.add('database-backup', {}, { repeat: { pattern: '0 2 * * *', tz: 'Europe/Berlin' }, jobId: 'daily-backup' // Verhindert Duplikate }); // Alle 5 Minuten await scheduledQueue.add('health-check', {}, { repeat: { every: 5 * 60 * 1000 // Millisekunden }, jobId: 'health-check' }); // Wöchentlich Montag 9:00 await scheduledQueue.add('weekly-report', { type: 'weekly' }, { repeat: { pattern: '0 9 * * 1', tz: 'Europe/Berlin' }, jobId: 'weekly-report' }); // Monatlich am 1. await scheduledQueue.add('monthly-cleanup', {}, { repeat: { pattern: '0 3 1 * *', tz: 'Europe/Berlin' }, jobId: 'monthly-cleanup' }); } // Worker für Scheduled Jobs const worker = new Worker('scheduled-tasks', async (job) => { console.log(`Processing ${job.name}`); switch (job.name) { case 'database-backup': await performDatabaseBackup(); break; case 'health-check': await runHealthChecks(); break; case 'weekly-report': await generateAndSendReport('weekly'); break; case 'monthly-cleanup': await cleanupOldData(); break; default: console.warn(`Unknown job: ${job.name}`); } return { completed: true }; }, { connection }); worker.on('completed', (job, result) => { console.log(`Job ${job.name} completed`); }); worker.on('failed', (job, error) => { console.error(`Job ${job?.name} failed:`, error); // Alert senden }); // Repeatable Jobs verwalten async function listScheduledJobs() { const jobs = await scheduledQueue.getRepeatableJobs(); return jobs.map(job => ({ name: job.name, pattern: job.pattern || `every ${job.every}ms`, next: new Date(job.next).toISOString() })); } async function removeScheduledJob(jobId: string) { const jobs = await scheduledQueue.getRepeatableJobs(); const job = jobs.find(j => j.id === jobId); if (job) { await scheduledQueue.removeRepeatableByKey(job.key); console.log(`Removed job: ${jobId}`); } } ``` --- ## Serverless Cron (Vercel/AWS) ```typescript // Vercel Cron Jobs (vercel.json) { "crons": [ { "path": "/api/cron/daily-backup", "schedule": "0 2 * * *" }, { "path": "/api/cron/hourly-sync", "schedule": "0 * * * *" } ] } // app/api/cron/daily-backup/route.ts import { NextRequest, NextResponse } from 'next/server'; export const runtime = 'edge'; export const maxDuration = 60; // 60 Sekunden max export async function GET(request: NextRequest) { // Verify Cron Secret (Security) const authHeader = request.headers.get('authorization'); if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } try { await performBackup(); return NextResponse.json({ success: true, timestamp: new Date().toISOString() }); } catch (error) { console.error('Backup failed:', error); return NextResponse.json({ success: false, error: (error as Error).message }, { status: 500 }); } } // AWS EventBridge + Lambda // serverless.yml // functions: // dailyBackup: // handler: src/handlers/backup.handler // events: // - schedule: cron(0 2 * * ? *) ``` --- ## Job Locking (Prevent Duplicates) ```typescript import { Redis } from 'ioredis'; const redis = new Redis(process.env.REDIS_URL!); async function withJobLock( jobName: string, ttlSeconds: number, handler: () => Promise ): Promise { const lockKey = `job-lock:${jobName}`; // Versuche Lock zu erwerben const acquired = await redis.set(lockKey, Date.now().toString(), 'EX', ttlSeconds, 'NX'); if (!acquired) { console.log(`Job ${jobName} already running, skipping`); return null; } try { return await handler(); } finally { // Lock freigeben await redis.del(lockKey); } } // Verwendung cron.schedule('*/5 * * * *', async () => { await withJobLock('sync-products', 300, async () => { // Dieser Code läuft garantiert nur einmal await syncProducts(); }); }); ``` --- ## Monitoring & Alerting ```typescript interface JobExecution { jobName: string; startedAt: Date; completedAt?: Date; status: 'running' | 'completed' | 'failed'; error?: string; duration?: number; } class JobMonitor { private executions: Map = new Map(); async trackExecution( jobName: string, handler: () => Promise ): Promise { const executionId = `${jobName}-${Date.now()}`; this.executions.set(executionId, { jobName, startedAt: new Date(), status: 'running' }); try { const result = await handler(); const execution = this.executions.get(executionId)!; execution.completedAt = new Date(); execution.status = 'completed'; execution.duration = execution.completedAt.getTime() - execution.startedAt.getTime(); // Log to database await this.saveExecution(execution); return result; } catch (error) { const execution = this.executions.get(executionId)!; execution.completedAt = new Date(); execution.status = 'failed'; execution.error = (error as Error).message; execution.duration = execution.completedAt.getTime() - execution.startedAt.getTime(); await this.saveExecution(execution); await this.sendAlert(execution); throw error; } finally { // Cleanup alte Executions setTimeout(() => this.executions.delete(executionId), 60000); } } private async saveExecution(execution: JobExecution) { await db.jobExecution.create({ data: execution }); } private async sendAlert(execution: JobExecution) { // Slack, Email, PagerDuty, etc. await slack.send({ text: `❌ Job Failed: ${execution.jobName}`, attachments: [{ color: 'danger', fields: [ { title: 'Error', value: execution.error || 'Unknown' }, { title: 'Duration', value: `${execution.duration}ms` } ] }] }); } } ``` --- ## Fazit Task Scheduling in Node.js: 1. **node-cron**: Einfach für Single-Server 2. **BullMQ**: Distributed, Reliable, Scalable 3. **Serverless Cron**: Vercel, AWS EventBridge 4. **Job Locking**: Verhindert Duplikate Zuverlässige Automation für jede Anwendung. --- ## Bildprompts 1. "Clock with automated tasks running at specific times, scheduling concept" 2. "Multiple servers coordinating scheduled jobs, distributed cron" 3. "Calendar with recurring events and checkmarks, automated workflow" --- ## Quellen - [node-cron Documentation](https://www.npmjs.com/package/node-cron) - [BullMQ Repeatable Jobs](https://docs.bullmq.io/guide/jobs/repeatable) - [Vercel Cron Jobs](https://vercel.com/docs/cron-jobs) - [Cron Expression Generator](https://crontab.guru/)