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.
Two Types of Scheduled Jobs
Section titled “Two Types of Scheduled Jobs”bunqueue supports two scheduling modes:
| Mode | Use Case | Example |
|---|---|---|
| Cron expression | Calendar-based schedules | ”Every Monday at 9am” |
| Repeat interval | Fixed interval | ”Every 5 minutes” |
Cron Expressions
Section titled “Cron Expressions”Use standard cron syntax for calendar-based schedules:
import { Queue } from 'bunqueue/client';
const queue = new Queue('reports', { embedded: true });
// Daily at midnight UTCawait queue.upsertJobScheduler('daily-report', { pattern: '0 0 * * *',}, { name: 'generate-report', data: { type: 'daily' },});
// Every Monday at 9:00 AMawait queue.upsertJobScheduler('weekly-digest', { pattern: '0 9 * * 1',}, { name: 'send-digest', data: { type: 'weekly' },});
// Every 15 minutesawait queue.upsertJobScheduler('health-check', { pattern: '*/15 * * * *',}, { name: 'check-health', data: { service: 'api' },});Repeat Intervals
Section titled “Repeat Intervals”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' },});Timezone Support
Section titled “Timezone Support”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 timeawait queue.upsertJobScheduler('evening-cleanup', { pattern: '0 18 * * *', tz: 'Asia/Tokyo',}, { name: 'cleanup', data: {},});Execution Limits
Section titled “Execution Limits”Prevent runaway cron jobs with execution limits:
// Run at most 100 times, then stopawait queue.upsertJobScheduler('limited-task', { pattern: '*/5 * * * *', limit: 100,}, { name: 'task', data: {},});Restart & Offline Behavior
Section titled “Restart & Offline Behavior”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.
Managing Scheduled Jobs
Section titled “Managing Scheduled Jobs”List, inspect, and remove schedulers:
// List all schedulersconst schedulers = await queue.getJobSchedulers();for (const s of schedulers) { console.log(s.name, s.pattern || s.every, s.next);}
// Get a specific schedulerconst scheduler = await queue.getJobScheduler('daily-report');console.log(scheduler);
// Remove a schedulerawait queue.removeJobScheduler('daily-report');
// Count schedulersconst count = await queue.getJobSchedulersCount();Event-Driven Scheduler Architecture
Section titled “Event-Driven Scheduler Architecture”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 setTimeoutThis means zero CPU usage between scheduled events. The scheduler only wakes up when a job needs to fire.
Cron + Workers: Complete Example
Section titled “Cron + Workers: Complete Example”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 hourawait 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 jobsconst 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`);});CLI Management
Section titled “CLI Management”Manage cron jobs from the command line:
# List all cron jobsbunqueue cron list
# Add a cron jobbunqueue cron add --name daily-report --queue reports \ --schedule "0 0 * * *" --data '{"type":"daily"}'
# Delete a cron jobbunqueue cron delete --name daily-report