@hazeljs/cron
Scheduled tasks as decorators. No setInterval hacks.
@Cron('0 9 * * *') for daily at 9am. @Interval(60000) for every minute. Real cron expressions, timezone support, overlap prevention. Background jobs that actually run when you expect.

Features
- ⏰ Cron Expressions - Standard cron syntax support
- 🎨 Decorator-Based API -
@Cron, @Interval, @Timeout
- 🔄 Job Management - Start, stop, and manage scheduled jobs
- 📊 Job Monitoring - Track execution history and status
- 🛡️ Error Handling - Automatic retry and error recovery
- 🎯 Timezone Support - Schedule jobs in specific timezones
- 🔒 Overlap Prevention - Prevent concurrent executions
- 📝 Logging - Built-in execution logging
Installation
npm install @hazeljs/cron
Quick Start
1. Import CronModule
import { HazelModule } from '@hazeljs/core';
import { CronModule } from '@hazeljs/cron';
@HazelModule({
imports: [CronModule.forRoot()],
})
export class AppModule {}
2. Create Scheduled Tasks
import { Injectable } from '@hazeljs/core';
import { Cron, CronExpression } from '@hazeljs/cron';
@Injectable()
export class TasksService {
@Cron(CronExpression.EVERY_MINUTE)
handleEveryMinute() {
console.log('This runs every minute');
}
@Cron('0 0 * * *')
handleMidnight() {
console.log('This runs at midnight every day');
}
@Cron('0 9 * * 1-5')
handleWeekdayMorning() {
console.log('This runs Monday-Friday at 9 AM');
}
}
Cron Expressions
Predefined Expressions
import { CronExpression } from '@hazeljs/cron';
@Cron(CronExpression.EVERY_SECOND)
@Cron(CronExpression.EVERY_5_SECONDS)
@Cron(CronExpression.EVERY_10_SECONDS)
@Cron(CronExpression.EVERY_30_SECONDS)
@Cron(CronExpression.EVERY_MINUTE)
@Cron(CronExpression.EVERY_5_MINUTES)
@Cron(CronExpression.EVERY_10_MINUTES)
@Cron(CronExpression.EVERY_30_MINUTES)
@Cron(CronExpression.EVERY_HOUR)
@Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT)
@Cron(CronExpression.EVERY_DAY_AT_NOON)
@Cron(CronExpression.EVERY_WEEK)
@Cron(CronExpression.EVERY_MONTH)
Custom Expressions
@Cron('0 */15 * * * *')
@Cron('0 0 12 * * *')
@Cron('0 0 0 1 * *')
@Cron('0 0 9 * * 1-5')
@Cron('0 30 11 * * 1,3,5')
Decorators
@Cron()
Schedule recurring tasks:
@Injectable()
export class TasksService {
@Cron('0 0 * * *', {
name: 'daily-cleanup',
timezone: 'America/New_York',
})
async dailyCleanup() {
console.log('Running daily cleanup');
await this.cleanupOldData();
}
}
@Interval()
Run tasks at fixed intervals:
@Injectable()
export class MonitoringService {
@Interval(5000)
checkHealth() {
console.log('Health check');
}
@Interval(60000, { name: 'metrics-collector' })
collectMetrics() {
console.log('Collecting metrics');
}
}
@Timeout()
Run tasks once after a delay:
@Injectable()
export class StartupService {
@Timeout(5000)
async warmupCache() {
console.log('Warming up cache');
await this.cacheService.warmup();
}
}
Job Management
Manual Job Control
import { Injectable } from '@hazeljs/core';
import { CronService, SchedulerRegistry } from '@hazeljs/cron';
@Injectable()
export class JobManager {
constructor(
private schedulerRegistry: SchedulerRegistry,
private cronService: CronService
) {}
stopJob(name: string) {
const job = this.schedulerRegistry.getCronJob(name);
job.stop();
console.log(`Job ${name} stopped`);
}
startJob(name: string) {
const job = this.schedulerRegistry.getCronJob(name);
job.start();
console.log(`Job ${name} started`);
}
deleteJob(name: string) {
this.schedulerRegistry.deleteCronJob(name);
console.log(`Job ${name} deleted`);
}
getAllJobs() {
return this.schedulerRegistry.getCronJobs();
}
}
Dynamic Job Creation
@Injectable()
export class DynamicJobService {
constructor(private schedulerRegistry: SchedulerRegistry) {}
addJob(name: string, cronExpression: string, callback: () => void) {
const job = new CronJob(cronExpression, callback);
this.schedulerRegistry.addCronJob(name, job);
job.start();
}
removeJob(name: string) {
this.schedulerRegistry.deleteCronJob(name);
}
}
Configuration
Module Configuration
CronModule.forRoot({
enabled: true,
timezone: 'UTC',
preventOverlap: true,
retry: {
attempts: 3,
delay: 1000,
},
logging: {
enabled: true,
logSuccess: true,
logErrors: true,
},
});
Job-Level Configuration
@Cron('0 0 * * *', {
name: 'backup-job',
timezone: 'America/New_York',
runOnInit: false,
preventOverlap: true,
retryAttempts: 3,
retryDelay: 5000,
})
async backupDatabase() {
await this.databaseService.backup();
}
Error Handling
@Injectable()
export class TasksService {
@Cron('0 0 * * *', {
name: 'risky-job',
retryAttempts: 3,
retryDelay: 5000,
})
async riskyJob() {
try {
await this.performRiskyOperation();
} catch (error) {
console.error('Job failed:', error);
throw error;
}
}
}
Use Cases
Database Cleanup
@Injectable()
export class DatabaseCleanupService {
@Cron('0 2 * * *')
async cleanupOldRecords() {
const thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
await this.db.logs.deleteMany({
where: { createdAt: { lt: thirtyDaysAgo } },
});
console.log('Old records cleaned up');
}
}
Report Generation
@Injectable()
export class ReportService {
@Cron('0 0 9 * * 1')
async generateWeeklyReport() {
const report = await this.analytics.generateWeeklyReport();
await this.email.send({
to: 'admin@example.com',
subject: 'Weekly Report',
body: report,
});
}
}
Cache Warming
@Injectable()
export class CacheService {
@Cron('0 */30 * * * *')
async warmupCache() {
const popularProducts = await this.db.products.findMany({
where: { popular: true },
});
for (const product of popularProducts) {
await this.cache.set(`product:${product.id}`, product, 3600);
}
}
}
Health Monitoring
@Injectable()
export class HealthMonitor {
@Interval(30000)
async checkServices() {
const services = ['database', 'redis', 'api'];
for (const service of services) {
const isHealthy = await this.checkServiceHealth(service);
if (!isHealthy) {
await this.alertService.sendAlert(`${service} is down!`);
}
}
}
}
Data Synchronization
@Injectable()
export class SyncService {
@Cron('0 */5 * * * *')
async syncData() {
const localData = await this.getLocalData();
const remoteData = await this.getRemoteData();
const diff = this.calculateDiff(localData, remoteData);
await this.applyChanges(diff);
}
}
Best Practices
- Use Named Jobs - Always provide a name for easier management
- Handle Errors - Implement proper error handling and logging
- Prevent Overlaps - Enable
preventOverlap for long-running jobs
- Set Appropriate Timezones - Specify timezone for time-sensitive jobs
- Monitor Execution - Track job execution and failures
- Test Thoroughly - Test cron expressions before deploying
- Use Intervals for Simple Tasks - Use
@Interval for fixed-interval tasks
- Cleanup Resources - Properly cleanup resources in job handlers
API Reference
Decorators
@Cron(expression: string, options?: CronOptions)
@Interval(milliseconds: number, options?: IntervalOptions)
@Timeout(milliseconds: number, options?: TimeoutOptions)
CronService
class CronService {
addCronJob(name: string, expression: string, callback: () => void): void;
getCronJob(name: string): CronJob;
deleteCronJob(name: string): void;
getCronJobs(): Map<string, CronJob>;
}
SchedulerRegistry
class SchedulerRegistry {
addCronJob(name: string, job: CronJob): void;
getCronJob(name: string): CronJob;
deleteCronJob(name: string): void;
getCronJobs(): Map<string, CronJob>;
addInterval(name: string, intervalId: NodeJS.Timeout): void;
deleteInterval(name: string): void;
addTimeout(name: string, timeoutId: NodeJS.Timeout): void;
deleteTimeout(name: string): void;
}
Examples
See the examples directory for complete working examples.
Testing
npm test
Contributing
Contributions are welcome! Please read our Contributing Guide for details.
License
Apache 2.0 © HazelJS
Links