DEV Community

Cover image for Mastering Cron Jobs in NestJS: A Complete Guide with Real Examples (and Related Scheduling Mechanisms)
Dawit Girma
Dawit Girma

Posted on

Mastering Cron Jobs in NestJS: A Complete Guide with Real Examples (and Related Scheduling Mechanisms)

  • How do you automatically send daily reports to users without manual intervention?
  • How can a system periodically clean up expired data such as sessions or tokens?
  • What is the most reliable way to execute a task every midnight?

For these types of problems, cron jobs provide an effective and scalable solution.

Cron jobs are a scheduling mechanism used to automate repetitive tasks in software systems by executing code at predefined times or intervals. Instead of relying on manual triggers or user actions, cron jobs enable applications to handle background operations such as:

  • Sending emails
  • Cleaning expired data
  • Generating reports
  • Synchronizing with external services

This approach improves efficiency, minimizes human error, and ensures that critical tasks run consistently and on time.

In modern backend development, frameworks like NestJS provide built-in support for task scheduling, allowing developers to implement cron-based automation in a clean, structured, and scalable way.

This article explores how to use cron jobs in NestJS, including:

  • Declarative scheduling
  • Dynamic scheduling
  • Interval-based execution
  • Timeout-based execution

Prerequisites

  • Basic knowledge of NestJS

Project Setup

  • Create a New NestJS Application
nest new nestjs-cron
Enter fullscreen mode Exit fullscreen mode
  • Install Scheduling Dependency
npm install --save @nestjs/schedule
Enter fullscreen mode Exit fullscreen mode
  • Configure Schedule Module

Update your app.module.ts:

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ScheduleModule } from '@nestjs/schedule';

@Module({
 imports: [ScheduleModule.forRoot()],
 controllers: [AppController],
 providers: [AppService],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

Now, we are all done with the setup, so we can create declarative and dynamic cron jobs.

Declarative Cron Jobs

Declarative cron jobs are defined before the application starts and run automatically based on a schedule.

Cron Expression Format

A cron expression typically consists of 6 fields:

*  *  *  *  *  *
|  |  |  |  |  |
|  |  |  |  |  └── Day of Week (0 - 7) (Sunday = 0 or 7)
|  |  |  |  └───── Month (1 - 12)
|  |  |  └──────── Day of Month (1 - 31)
|  |  └─────────── Hour (0 - 23)
|  └────────────── Minute (0 - 59)
└───────────────── Second (0 - 59)
Enter fullscreen mode Exit fullscreen mode

Note: The seconds field is optional in some implementations.

The asterisk (*) represents “every” value.

For example:

  • * in the minute field → every minute
  • * * * * * → runs every minute

If interpreting cron expressions becomes challenging, you can use online tools such as crontab.guru to simplify the process. This tool allows you to input or modify cron expression values and instantly displays a human-readable explanation of the schedule. By adjusting the fields, you can quickly understand how each change affects execution timing. An illustrative example is shown below.

NestJS also provides predefined expressions for readability using CronExpression enum. This approach is easier to understand but less flexible than custom expressions. E.g.

CronExpression.EVERY_10_MINUTES
Enter fullscreen mode Exit fullscreen mode

Below is example service created and imported in app.module.ts to illustrate.

import { Injectable, Logger } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';


@Injectable()
export class TestCronService {
 private readonly logger = new Logger(TestCronService.name);


 // Runs every minute using asterisk expression
 @Cron('* * * * *', {
   name: 'asterisk-every-minute',
   timeZone: 'Africa/Addis_Ababa',
 })
 handleAsteriskEveryMinute() {
   this.logger.log('Running cron: asterisk-every-minute');
 }


 // Runs every minute using CronExpression enum
 @Cron(CronExpression.EVERY_MINUTE, {
   name: 'enum-every-minute',
   timeZone: 'Africa/Addis_Ababa',
 })
 handleEnumEveryMinute() {
   this.logger.log('Running cron: enum-every-minute');
 }
}


Enter fullscreen mode Exit fullscreen mode

And the provider section of app.module.ts should look like

 providers: [AppService, TestCronService],
Enter fullscreen mode Exit fullscreen mode

And finally run your program, you should see logs of the above two cron jobs. Like

Declarative Intervals

Intervals allow repeated execution at fixed time intervals using setInterval() internally. Example code and log image is provided below.

 // Runs every 2 seconds using declarative interval
 @Interval('every-2-seconds', 2000)
 handleEvery2Seconds() {
   this.logger.log('declarative interval every 2 seconds (2000ms)');
 }

Enter fullscreen mode Exit fullscreen mode

Declarative Timeout

Timeouts execute a task only once after a specified delay. Internally, they rely on JavaScript’s setTimeout() function to schedule the execution.

 // Runs once after 5 seconds using declarative timeout
 @Timeout('after-5-seconds', 5000)
 handleAfter5Seconds() {
   this.logger.log('declarative timeout after 5 seconds (5000ms)');
 }


Enter fullscreen mode Exit fullscreen mode

Dynamic cron Jobs

Dynamic cron jobs enable runtime management of scheduled tasks, allowing you to create, retrieve, update, or delete cron jobs while the application is running. To perform these operations, NestJS provides the SchedulerRegistry API, which must be injected into your service to gain programmatic control over the scheduling system.

Key methods

  • addCronJob() – register a job
  • getCronJob() – retrieve a job
  • deleteCronJob() – remove a job

Cron job controls after we get cron job by name

  • stop() – stop execution
  • start() – restart job
  • setTime() – update schedule
  • lastDate() – last execution time
  • nextDate() – next execution time

Example code is given below with its log.

import { Injectable, Logger } from '@nestjs/common';
import { SchedulerRegistry, Timeout } from '@nestjs/schedule';
import { CronJob, CronTime } from 'cron';
import { DateTime } from 'luxon';


@Injectable()
export class DynamicCronService {
 private readonly logger = new Logger(DynamicCronService.name);


 constructor(private readonly schedulerRegistry: SchedulerRegistry) {}


 // Creates dynamic-job after 10 seconds and demonstrates all scheduler methods
 @Timeout('create-dynamic-cron', 10000)
 async createDynamicCron() {
   const job = new CronJob('* * * * * *', () => {
     this.logger.log('[dynamic-job] tick');
   });


   this.schedulerRegistry.addCronJob('dynamic-job', job);
   job.start();
   this.logger.log('dynamic-job created and started (every second)');


   const cronJob = this.schedulerRegistry.getCronJob('dynamic-job');


   // stop() - stops the running cron job
   cronJob.stop();
   this.logger.log('stop()     — dynamic-job stopped');


   // start() - restarts the stopped cron job
   cronJob.start();
   this.logger.log('start()    — dynamic-job restarted');


   // setTime() - stops the job and restarts it with a new schedule (every 5 seconds)
   cronJob.setTime(new CronTime('*/5 * * * * *'));
   cronJob.start();
   this.logger.log(
     'setTime()  — schedule changed to every 5 seconds, job restarted',
   );


   // wait 5 seconds so the job executes at least once before checking lastDate()
   await new Promise((resolve) => setTimeout(resolve, 5000));


   // lastDate() - returns last DateTime of execution (null if not yet executed)
   this.logger.log(
     `lastDate() — last execution: ${cronJob.lastDate() ? DateTime.fromJSDate(cronJob.lastDate()!) : 'not yet executed'}`,
   );


   // nextDate() - returns next DateTime of execution as a luxon DateTime
   this.logger.log(`nextDate() — next execution: ${cronJob.nextDate()}`);
 }


 // Stops and deletes dynamic-job after 30 seconds
 @Timeout('delete-dynamic-cron', 30000)
 deleteDynamicCron() {
   const cronJob = this.schedulerRegistry.getCronJob('dynamic-job');
   cronJob.stop();
   this.schedulerRegistry.deleteCronJob('dynamic-job');
   this.logger.log('dynamic-job stopped and deleted after 30 seconds');
 }
}




Enter fullscreen mode Exit fullscreen mode

Dynamic Intervals

Intervals can be managed at runtime using the SchedulerRegistry API, allowing you to create, retrieve, and delete named intervals dynamically. For clarity, the previous provider can be replaced with a dedicated dynamic interval service, which should then be registered in app.module.ts.

The following example demonstrates the complete lifecycle of a dynamic interval: an interval is created after 5 seconds, retrieved for reference, and automatically removed after running for 20 seconds.


import { Injectable, Logger } from '@nestjs/common';
import { SchedulerRegistry, Timeout } from '@nestjs/schedule';


@Injectable()
export class DynamicIntervalService {
 private readonly logger = new Logger(DynamicIntervalService.name);


 constructor(private readonly schedulerRegistry: SchedulerRegistry) {}


 // Creates dynamic-interval after 5 seconds and registers it with SchedulerRegistry
 @Timeout('create-dynamic-interval', 5000)
 createDynamicInterval() {
   const interval = setInterval(() => {
     this.logger.log('[dynamic-interval] tick every 2 seconds');
   }, 2000);


   this.schedulerRegistry.addInterval('dynamic-interval', interval);
   this.logger.log('dynamic-interval created and started (every 2 seconds)');


   // getInterval() - retrieves the registered interval reference
   const ref = this.schedulerRegistry.getInterval('dynamic-interval');
   this.logger.log(`getInterval() — interval ref retrieved, id: ${ref}`);
 }


 // Deletes dynamic-interval after 20 seconds
 @Timeout('delete-dynamic-interval', 20000)
 deleteDynamicInterval() {
   this.schedulerRegistry.deleteInterval('dynamic-interval');
   this.logger.log('dynamic-interval deleted after 20 seconds');
 }
}
Enter fullscreen mode Exit fullscreen mode

Dynamic Timeouts

Timeouts can also be managed dynamically at runtime using the SchedulerRegistry API, which allows you to create, retrieve, and delete timeout tasks programmatically. For clarity, you can replace the previous provider with a dedicated dynamic timeout service and register it in app.module.ts.

The following example illustrates the full lifecycle of a dynamic timeout: a timeout is created after 5 seconds, its reference is retrieved for inspection, and it is subsequently deleted after 20 seconds.


import { Injectable, Logger } from '@nestjs/common';
import { SchedulerRegistry, Timeout } from '@nestjs/schedule';


@Injectable()
export class DynamicTimeoutService {
 private readonly logger = new Logger(DynamicTimeoutService.name);


 constructor(private readonly schedulerRegistry: SchedulerRegistry) {}


 // Creates dynamic-timeout after 5 seconds and registers it with SchedulerRegistry
 @Timeout('create-dynamic-timeout', 5000)
 createDynamicTimeout() {
   const timeout = setTimeout(() => {
     this.logger.log('[dynamic-timeout] fired after 10 seconds');
   }, 10000);


   this.schedulerRegistry.addTimeout('dynamic-timeout', timeout);
   this.logger.log(
     'dynamic-timeout created and registered (fires in 10 seconds)',
   );


   // getTimeout() - retrieves the registered timeout reference
   const ref = this.schedulerRegistry.getTimeout('dynamic-timeout');
   this.logger.log(`getTimeout() — timeout ref retrieved, id: ${ref}`);
 }


 // Deletes dynamic-timeout after 20 seconds (before it fires)
 @Timeout('delete-dynamic-timeout', 20000)
 deleteDynamicTimeout() {
   this.schedulerRegistry.deleteTimeout('dynamic-timeout');
   this.logger.log('dynamic-timeout deleted after 20 seconds ');
 }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

NestJS provides a powerful and flexible scheduling system through @nestjs/schedule, enabling developers to implement:

  • Cron-based automation
  • Interval-based execution
  • Timeout-based execution
  • Runtime (dynamic) scheduling control

By leveraging these features, you can build reliable backend systems that automate repetitive tasks efficiently and maintain consistent system behavior.

Contact

If you have any questions, feel free to reach out:

The final code is available at public repository:
https://github.com/dedawit/nestjs-cron.git

References

  1. https://dev.to/jay818/cron-jobs-in-nest-js-1g43
  2. https://dev.to/mohinsheikh/cron-jobs-a-comprehensive-guide-from-basics-to-advanced-usage-2p40
  3. https://docs.nestjs.com/techniques/task-scheduling

Top comments (0)