NestJS Task Scheduling
Task scheduling runs code automatically at specified times or intervals — without any user triggering a request. Sending weekly report emails, clearing expired tokens every hour, syncing data from an external API every 10 minutes — all of these require scheduled tasks. NestJS provides the @nestjs/schedule package that wraps the node-cron library with a clean, decorator-based API.
Installing the Schedule Package
npm install @nestjs/schedule npm install -D @types/cron
Enabling the Scheduler
// app.module.ts
import { ScheduleModule } from '@nestjs/schedule';
@Module({
imports: [ ScheduleModule.forRoot() ],
})
export class AppModule {}
Three Types of Scheduled Tasks
Type | Decorator | When it runs -----------|--------------|------------------------------- Cron job | @Cron() | At a specific time (cron expr) Interval | @Interval() | Every N milliseconds Timeout | @Timeout() | Once, after N milliseconds
Cron Jobs
A cron expression defines a precise schedule using five or six time fields. The six-field format includes seconds:
* * * * * * | | | | | Day of week (0-7) | | | | Month (1-12) | | | Day of month (1-31) | | Hour (0-23) | Minute (0-59) Second (0-59) [optional] Examples: 0 * * * * * - every minute at second 0 0 0 9 * * * - every day at 9:00 AM 0 0 9 * * 1 - every Monday at 9:00 AM 0 0 0 1 * * - first day of every month at midnight
import { Injectable } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
@Injectable()
export class TasksService {
@Cron(CronExpression.EVERY_HOUR)
clearExpiredTokens() {
this.authService.deleteExpiredTokens();
}
@Cron('0 9 * * 1') // Every Monday at 9 AM
sendWeeklyReport() {
this.emailService.sendWeeklyReport();
}
@Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT)
archiveOldLogs() {
this.logsService.archiveOlderThan(30);
}
}
Built-In CronExpression Constants
CronExpression.EVERY_SECOND CronExpression.EVERY_10_SECONDS CronExpression.EVERY_MINUTE CronExpression.EVERY_5_MINUTES CronExpression.EVERY_HOUR CronExpression.EVERY_DAY_AT_MIDNIGHT CronExpression.EVERY_DAY_AT_NOON CronExpression.EVERY_WEEK CronExpression.EVERY_MONTH
Interval Tasks
An interval task runs every N milliseconds, repeatedly, for the entire application lifetime:
@Interval(10000) // every 10 seconds
checkApiHealth() {
this.healthService.ping('https://external-api.com/health');
}
@Interval(60 * 60 * 1000) // every 1 hour
syncExternalData() {
this.syncService.pullLatestData();
}
Timeout Tasks
A timeout task runs once after a delay from application startup:
@Timeout(5000) // 5 seconds after app starts, runs once
sendStartupNotification() {
this.notificationService.notifyAdmins('Server started');
}
Registering the Task Service
@Module({
providers: [TasksService],
})
export class TasksModule {}
Dynamic Cron Jobs
The SchedulerRegistry lets you add, remove, and stop cron jobs at runtime — useful when users configure their own notification schedules:
import { SchedulerRegistry } from '@nestjs/schedule';
import { CronJob } from 'cron';
@Injectable()
export class DynamicTasksService {
constructor(private schedulerRegistry: SchedulerRegistry) {}
addUserJob(userId: string, cronTime: string) {
const job = new CronJob(cronTime, () => {
this.sendUserNotification(userId);
});
this.schedulerRegistry.addCronJob(`user-${userId}`, job);
job.start();
}
removeUserJob(userId: string) {
this.schedulerRegistry.deleteCronJob(`user-${userId}`);
}
}
Task Scheduling Diagram
Application starts
|
v
ScheduleModule scans all providers
|
v
Finds @Cron, @Interval, @Timeout decorators
|
v
Registers each task with the scheduler
Timeline:
T+5s → sendStartupNotification() [once]
T+10s,20s,... → checkApiHealth() [every 10s]
T+1h,2h,... → clearExpiredTokens() [every hour]
Mon 9:00 AM → sendWeeklyReport() [weekly]
Best Practices for Scheduled Tasks
Keep scheduled task methods short. Each method should call a service that performs the real work — the same pattern as controllers calling services. Long-running tasks must run asynchronously with async/await and handle errors internally so that a single task failure does not affect the rest of the scheduler.
Add logging at the start and end of every scheduled task. In production, you need confirmation that tasks ran, how long they took, and whether any errors occurred. Without logs, a broken nightly cleanup job can go undetected for days. Visibility into scheduled task execution is as important as logging HTTP requests.
