# 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<void>;
  enabled: boolean;
  timezone?: string;
}

class TaskScheduler {
  private tasks: Map<string, ScheduledTask> = new Map();
  private jobs: Map<string, Job> = 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<T>(
  jobName: string,
  ttlSeconds: number,
  handler: () => Promise<T>
): Promise<T | null> {
  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<string, JobExecution> = new Map();

  async trackExecution<T>(
    jobName: string,
    handler: () => Promise<T>
  ): Promise<T> {
    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/)
