Features
Docs
CLI
Benchmarks
Examples

© 2024 MoroJs

Background Jobs

Production-grade background job scheduling with cron expressions, intervals, automatic retries, circuit breakers, and distributed system support. Zero dependencies required.

Background Jobs That Just Work

Schedule jobs with cron expressions or intervals.
Automatic retries, circuit breakers, and distributed system support included.

It's This Simple

Schedule background jobs

typescript

1import { createApp } from '@morojs/moro';
2
3const app = createApp({
4  jobs: {
5    enabled: true, // Enable job scheduler
6  },
7});
8
9// Simple cron job - runs daily at 2 AM
10app.job('cleanup-old-data', '0 2 * * *', async () => {
11  await database.cleanupOldRecords();
12});
13
14// Interval-based job - runs every 5 minutes
15app.job('health-check', '5m', async (ctx) => {
16  console.log(`Health check ${ctx.executionId}`);
17  await checkSystemHealth();
18});

Why Background Jobs Matter

Without proper job scheduling, you're manually managing cron tasks, handling failures, and dealing with distributed systems. With MoroJS, you get all of that automatically.

Traditional job scheduling requires external services and complex setup. We handle that automatically.

Without Job System

  • External cron services required
  • Manual retry logic
  • No circuit breakers
  • Complex distributed coordination

With MoroJS

  • Built-in job scheduler
  • Automatic retries with backoff
  • Per-job circuit breakers
  • Leader election for clusters

It's This Easy

Schedule jobs with cron expressions or intervals. That's it.

Cron expressions and intervals

typescript

1// Cron expressions
2app.job('backup', '0 3 * * *', backupDatabase); // Daily at 3 AM
3app.job('report', '0 9 * * 1', weeklyReport); // Mondays at 9 AM
4
5// Interval strings
6app.job('health-check', '5m', healthCheck); // Every 5 minutes
7app.job('cache-cleanup', '1h', cleanCache); // Every hour
8
9// Cron macros
10app.job('hourly-task', '@hourly', task);
11app.job('daily-task', '@daily', task);

Why It Makes Sense

Production-Ready

Automatic retries, circuit breakers, timeouts, and memory leak detection.

Distributed

Leader election, Kubernetes awareness, and cluster mode support.

Zero Dependencies

Built-in scheduler. No external services required.

How It Works

MoroJS includes a production-grade job scheduler that supports cron expressions, interval-based scheduling, automatic retries with exponential backoff, circuit breakers, and distributed system coordination. Jobs can be scheduled with simple cron expressions or interval strings, and the system handles execution, retries, and failure recovery automatically.

Quick Start

Basic Job Scheduling

typescript

1import { createApp } from '@morojs/moro';
2
3const app = createApp({
4  jobs: {
5    enabled: true, // Enable job scheduler
6  },
7});
8
9// Simple cron job - runs daily at 2 AM
10app.job('cleanup-old-data', '0 2 * * *', async () => {
11  await database.cleanupOldRecords();
12});
13
14// Interval-based job - runs every 5 minutes
15app.job('health-check', '5m', async (ctx) => {
16  console.log(`Health check ${ctx.executionId}`);
17  await checkSystemHealth();
18});
19
20// Advanced configuration
21app.job('generate-report', '@daily', async (ctx) => {
22  const report = await generateDailyReport();
23  return report;
24}, {
25  timeout: 60000, // 1 minute timeout
26  maxRetries: 3,
27  retryBackoff: 'exponential',
28  priority: 10, // Higher priority
29  onError: (ctx, error) => {
30    console.error(`Report generation failed`, error);
31  }
32});
33
34app.listen(3000);

Schedule Formats

typescript

1// Standard cron (minute hour day month weekday)
2app.job('backup', '0 3 * * *', backupDatabase); // Daily at 3 AM
3app.job('report', '0 9 * * 1', weeklyReport); // Mondays at 9 AM
4
5// Cron macros
6app.job('hourly-task', '@hourly', task);
7app.job('daily-task', '@daily', task);
8app.job('weekly-task', '@weekly', task);
9app.job('monthly-task', '@monthly', task);
10
11// Interval strings
12app.job('health-check', '5m', healthCheck); // Every 5 minutes
13app.job('cache-cleanup', '1h', cleanCache); // Every hour
14app.job('quick-task', '30s', quickTask); // Every 30 seconds
15app.job('nightly-job', '1d', nightlyJob); // Every day

Advanced Features

Job Management

typescript

1// Register a job
2const jobId = app.job('my-job', '*/5 * * * *', handler, {
3  name: 'My Custom Job Name',
4  enabled: true,
5  priority: 5,
6  maxConcurrent: 1,
7  timeout: 60000,
8  maxRetries: 3,
9  metadata: { owner: 'team-a' },
10});
11
12// Enable/Disable jobs
13app.setJobEnabled(jobId, false); // Disable job
14app.setJobEnabled(jobId, true); // Enable job
15
16// Manually trigger a job
17await app.triggerJob(jobId, { reason: 'manual-trigger' });

Priority Queue and Concurrency Control

typescript

1// Jobs with higher priority execute first
2app.job('critical-task', '*/1 * * * *', criticalTask, { priority: 10 });
3app.job('normal-task', '*/1 * * * *', normalTask, { priority: 5 });
4app.job('low-priority', '*/1 * * * *', lowPriorityTask, { priority: 1 });
5
6// Global concurrency limit (in config)
7const app = createApp({
8  jobs: { maxConcurrentJobs: 10 },
9});
10
11// Per-job concurrency limit
12app.job('scraper', '*/5 * * * *', scrape, {
13  maxConcurrent: 3, // Allow up to 3 concurrent executions
14});

Circuit Breaker

typescript

1// Automatically stops retrying failing jobs
2app.job('flaky-api', '*/1 * * * *', callFlakyAPI, {
3  enableCircuitBreaker: true,
4  maxRetries: 5,
5});
6
7// Circuit breaker opens after 5 consecutive failures
8// Jobs won't execute until circuit breaker resets (60s default)

Leader Election (Distributed Systems)

typescript

1const app = createApp({
2  jobs: {
3    enabled: true,
4    leaderElection: {
5      enabled: true,
6      strategy: 'file', // 'file' | 'redis' | 'none'
7      lockPath: '/tmp/moro-jobs-leader.lock',
8      lockTimeout: 30000,
9      heartbeatInterval: 10000,
10    },
11  },
12});

Job Metrics and Monitoring

typescript

1// Get job metrics
2const metrics = app.getJobMetrics(jobId);
3console.log(metrics);
4// {
5//   successRate: 98.5,
6//   failureRate: 1.5,
7//   averageDuration: 1234,
8//   totalExecutions: 200,
9//   recentFailures: 0
10// }
11
12// Check job health
13const health = app.getJobHealth(jobId);
14console.log(health);
15// {
16//   jobId: 'job_...',
17//   name: 'cleanup-old-data',
18//   status: 'healthy',
19//   enabled: true,
20//   lastExecution: Date,
21//   consecutiveFailures: 0,
22//   nextRun: Date
23// }
24
25// Get scheduler statistics
26const stats = app.getJobStats();
27console.log(stats);
28// {
29//   totalJobs: 5,
30//   enabledJobs: 4,
31//   runningJobs: 2,
32//   queuedJobs: 0,
33//   isLeader: true,
34//   isStarted: true
35// }
36
37// Subscribe to job events
38app.on('job:start', ({ jobId, executionId }) => {
39  console.log(`Job ${jobId} started: ${executionId}`);
40});
41
42app.on('job:complete', ({ jobId, result, duration }) => {
43  console.log(`Job ${jobId} completed in ${duration}ms`);
44});
45
46app.on('job:error', ({ jobId, error }) => {
47  console.error(`Job ${jobId} failed:`, error);
48});
49
50app.on('circuit-breaker:open', ({ jobId }) => {
51  console.error(`Circuit breaker opened for job ${jobId}`);
52});

Production-Ready Features

Production-Ready Resilience

  • Automatic retries with exponential backoff
  • Per-job circuit breakers
  • Timeout enforcement
  • Memory leak detection
  • Graceful shutdown

Distributed Systems

  • Leader election (file or Redis)
  • Kubernetes awareness
  • Cluster mode support
  • Crash recovery
  • Full observability & metrics

Next Steps