Skip to content

Cron Job Scheduling in bunqueue: Recurring Tasks for Bun Applications

Background jobs aren’t always triggered by user actions. Many tasks need to run on a schedule: daily reports, hourly cleanups, periodic syncs. bunqueue has a built-in cron scheduler that runs alongside your job queue with zero additional infrastructure.

bunqueue supports two scheduling modes:

ModeUse CaseExample
Cron expressionCalendar-based schedules”Every Monday at 9am”
Repeat intervalFixed interval”Every 5 minutes”

Use standard cron syntax for calendar-based schedules:

import { Queue } from 'bunqueue/client';
const queue = new Queue('reports', { embedded: true });
// Daily at midnight UTC
await queue.upsertJobScheduler('daily-report', {
pattern: '0 0 * * *',
}, {
name: 'generate-report',
data: { type: 'daily' },
});
// Every Monday at 9:00 AM
await queue.upsertJobScheduler('weekly-digest', {
pattern: '0 9 * * 1',
}, {
name: 'send-digest',
data: { type: 'weekly' },
});
// Every 15 minutes
await queue.upsertJobScheduler('health-check', {
pattern: '*/15 * * * *',
}, {
name: 'check-health',
data: { service: 'api' },
});

For simple fixed-interval schedules, use every:

// Every 5 minutes (300,000 ms)
await queue.upsertJobScheduler('sync-data', {
every: 300_000,
}, {
name: 'sync',
data: { source: 'external-api' },
});

Cron expressions default to UTC. Specify a timezone for local-time scheduling:

// 9:00 AM New York time (handles DST automatically)
await queue.upsertJobScheduler('morning-report', {
pattern: '0 9 * * *',
tz: 'America/New_York',
}, {
name: 'morning-report',
data: {},
});
// 6:00 PM Tokyo time
await queue.upsertJobScheduler('evening-cleanup', {
pattern: '0 18 * * *',
tz: 'Asia/Tokyo',
}, {
name: 'cleanup',
data: {},
});

Prevent runaway cron jobs with execution limits:

// Run at most 100 times, then stop
await queue.upsertJobScheduler('limited-task', {
pattern: '*/5 * * * *',
limit: 100,
}, {
name: 'task',
data: {},
});

Control what happens when the server restarts or workers go offline:

await queue.upsertJobScheduler('sync-data', {
pattern: '* * * * *',
timezone: 'America/New_York',
// Skip missed runs when the SERVER restarts (don't catch up)
skipMissedOnRestart: true,
// Skip job creation when no WORKER is connected to the queue
skipIfNoWorker: true,
}, {
data: { task: 'sync' },
});
  • skipMissedOnRestart: When the server restarts after downtime, cron jobs that were missed are skipped. The scheduler recalculates the next run time to the future instead of executing all missed runs.
  • skipIfNoWorker: When the cron fires but no worker is registered for the queue, the job is not created. This prevents job accumulation when workers go offline while the server keeps running. The cron continues to advance its schedule normally.

List, inspect, and remove schedulers:

// List all schedulers
const schedulers = await queue.getJobSchedulers();
for (const s of schedulers) {
console.log(s.name, s.pattern || s.every, s.next);
}
// Get a specific scheduler
const scheduler = await queue.getJobScheduler('daily-report');
console.log(scheduler);
// Remove a scheduler
await queue.removeJobScheduler('daily-report');
// Count schedulers
const count = await queue.getJobSchedulersCount();

bunqueue’s scheduler uses an event-driven design with precise setTimeout instead of polling:

Scheduler Tick
├── Calculate next cron/repeat fire time
├── setTimeout(fireTime - now)
└── On fire:
├── Create job in the queue
├── Update execution count
├── Calculate next fire time
└── Schedule next setTimeout

This means zero CPU usage between scheduled events. The scheduler only wakes up when a job needs to fire.

Here’s a complete pattern for a scheduled data sync:

import { Queue, Worker } from 'bunqueue/client';
const queue = new Queue('sync', { embedded: true });
// Schedule: every hour
await queue.upsertJobScheduler('hourly-sync', {
pattern: '0 * * * *',
}, {
name: 'sync-users',
data: { source: 'external-api' },
opts: {
attempts: 3,
backoff: { type: 'exponential', delay: 5000 },
timeout: 120_000, // 2 minute timeout
},
});
// Worker processes the scheduled jobs
const worker = new Worker('sync', async (job) => {
const { source } = job.data;
await job.log(`Starting sync from ${source}`);
const users = await fetchUsersFromAPI(source);
await job.updateProgress(50);
await saveUsersToDatabase(users);
await job.updateProgress(100);
return { synced: users.length };
}, { embedded: true });
worker.on('completed', (job, result) => {
console.log(`Synced ${result.synced} users`);
});

Manage cron jobs from the command line:

Terminal window
# List all cron jobs
bunqueue cron list
# Add a cron job
bunqueue cron add --name daily-report --queue reports \
--schedule "0 0 * * *" --data '{"type":"daily"}'
# Delete a cron job
bunqueue cron delete --name daily-report