Task SchedulingTechniques · NestJS

Work on a clock rather than on a request: feed the cats on an interval, open the shelter once after boot, report on a cron schedule, and pause and resume a named task through the SchedulerRegistry.

What you will learn

Read the theory for Task Scheduling

All Techniques lessons

All NestJS courses

loading types…

What you'll learn

  • Register ScheduleModule and schedule methods with @Cron, @Interval and @Timeout
  • Read a cron pattern and use CronExpression and the options that matter (name, timeZone, waitForCompletion)
  • Stop, start, add and delete named tasks at runtime through the SchedulerRegistry

Task Scheduling

Some work is not a response to a request. The cats are fed at fixed times whoever is on the website; a report goes out every night; a cleanup runs on the hour. Node has setInterval and setTimeout, and cron, the Unix scheduler, has a little language for "every weekday at 11:30". @nestjs/schedule brings both into a Nest provider as decorators on methods, so a scheduled task is written like any other method of a service and has the service's dependencies, and gives you a registry to start and stop tasks at runtime.

ScheduleModule

Register it once, in the root module:

@Module({
  imports: [ScheduleModule.forRoot()],
})
export class AppModule {}

At startup Nest scans every provider for scheduled methods and registers them. Nothing runs before the application has finished booting.

Cron jobs

@Cron() takes a cron pattern, six fields with an optional seconds field first:

* * * * * *
| | | | | |
| | | | | day of week
| | | | months
| | | day of month
| | hours
| minutes
seconds (optional)

@Cron('45 * * * * *') runs when the seconds hand hits 45, every minute; 0 30 11 * * 1-5 is 11:30 on weekdays; 0 */30 9-17 * * * every half hour during office hours. CronExpression names the common ones, CronExpression.EVERY_30_SECONDS, EVERY_DAY_AT_MIDNIGHT, so nobody has to count asterisks. A Date runs the method once at that moment. The options object takes name, for the registry; timeZone, since a shelter in Madrid and a server in Virginia disagree about 11:30; waitForCompletion, to skip a tick while the previous one is still running; and disabled.

Intervals and timeouts

@Interval(10000)
handleInterval() {
  this.logger.debug('Called every 10 seconds');
}

@Timeout(5000)
handleTimeout() {
  this.logger.debug('Called once after 5 seconds');
}

@Interval is setInterval; @Timeout is setTimeout, once after boot, for warm-ups and delayed starts. Both take an optional name as their first argument, @Interval('notifications', 2500), which is how the registry finds them later.

The registry

SchedulerRegistry is the runtime side, injected like any provider. getCronJob('name') returns the job, with stop(), start(), lastDate(), nextDate() and setTime(); addCronJob(name, new CronJob(pattern, callback)) registers one built at runtime with the cron package's class; deleteCronJob(name) removes it. Intervals and timeouts have the same trio: getInterval, addInterval(name, setInterval(...)), deleteInterval, and getTimeout, addTimeout, deleteTimeout. doesExist('interval', name) asks before touching. Deleting an interval clears it; adding one registers a handle you created, so the registry can clear it when the application shuts down.

Your task

The cats are fed on a clock, and the staff can pause it.

  1. AppModule turns scheduling on.
  2. FeedingService: a feeding round every 200 ms as a named interval feeding; the shelter opens once, 100 ms after boot; a report every second on the cron clock.
  3. pause() deletes the feeding interval through the registry; resume() registers a fresh one under the same name, calling the same round.

The graded requests wait for the clock: the status is read after enough time for rounds and a report to have run, and again after a pause. Run and watch the console: the rounds and the report interleave.

When it fails

  • rounds stays at 0: ScheduleModule.forRoot() is missing, so no decorator was registered; the methods exist and nobody calls them.
  • Nest can't resolve dependencies of the FeedingService (CatsService, ?), argument SchedulerRegistry: same cause; the registry is provided by the module.
  • No Interval was found with the given name (feeding): deleteInterval on a name that does not exist; the interval was never named, or was already paused. Ask doesExist first.
  • Rounds keep counting while paused: pause() set a flag but the interval still runs; the registry, not the flag, stops it.

Remember

  • ScheduleModule.forRoot() once; @Cron, @Interval and @Timeout on provider methods.
  • A cron pattern is six fields, seconds first; CronExpression names the usual ones; timeZone matters.
  • Name a task to reach it later; SchedulerRegistry starts, stops, adds and deletes by name.
  • A scheduled method has its service's dependencies, so a task can do real work.
Stuck? Show a hint

app.module.ts: ScheduleModule.forRoot() from '@nestjs/schedule'. feeding.service.ts: @Interval('feeding', 200) on feedingRound, @Timeout(100) on openShelter, @Cron(CronExpression.EVERY_SECOND) on report; inject SchedulerRegistry; pause(): if (this.registry.doesExist('interval', 'feeding')) this.registry.deleteInterval('feeding'); resume(): this.registry.addInterval('feeding', setInterval(() => void this.feedingRound(), 200)).