Introduction
Cron jobs are essential when you need to execute scheduled tasks in your application. But what happens when you have multiple instances running? 🤯 Without proper handling, each instance might execute the job simultaneously, causing duplicate processing and potential data inconsistencies.
A solid solution is using Bull (a Node.js queue library based on Redis) to ensure that only one instance executes the cron job at a time. In this article, we'll explore how to achieve this using NestJS and Bull.
🛠 Setting Up Bull in NestJS
1️⃣ Install Dependencies
First, install Bull and Redis client:
npm install --save @nestjs/bull bull ioredis
2️⃣ Configure BullModule
In your app.module.ts
, configure Bull to use Redis:
import { Module } from '@nestjs/common';
import { BullModule } from '@nestjs/bull';
import { MyCronJobProcessor } from './cron.processor';
import { MyCronJobService } from './cron.service';
@Module({
imports: [
BullModule.forRoot({
redis: {
host: 'localhost', // Use the Redis host
port: 6379, // Default Redis port
},
}),
BullModule.registerQueue({
name: 'cronQueue',
}),
],
providers: [MyCronJobProcessor, MyCronJobService],
})
export class AppModule {}
Note: Ensure Redis is running locally or use a cloud-hosted Redis service.
🎯 Implementing the Cron Job
3️⃣ Creating the Cron Job Service
We’ll create a service that adds jobs to the queue at scheduled intervals.
import { Injectable } from '@nestjs/common';
import { InjectQueue } from '@nestjs/bull';
import { Queue } from 'bull';
import { Cron } from '@nestjs/schedule';
@Injectable()
export class MyCronJobService {
constructor(@InjectQueue('cronQueue') private cronQueue: Queue) {}
@Cron('*/5 * * * * *') // Runs every 5 seconds
async scheduleJob() {
await this.cronQueue.add('processData', {});
console.log('Cron job added to the queue ✅');
}
}
The @Cron
decorator schedules the job at a fixed interval, ensuring that the task is queued rather than executed by every instance.
4️⃣ Processing the Cron Job
Only one instance should process the job at a time. Bull takes care of that for us! 🎉
import { Process, Processor } from '@nestjs/bull';
import { Job } from 'bull';
@Processor('cronQueue')
export class MyCronJobProcessor {
@Process('processData')
async handleCronJob(job: Job) {
console.log('Processing cron job... 🤖', job.id);
// Your task logic here (e.g., database cleanup, report generation)
}
}
📦 Create a Docker compose
If you run multiple instances of your NestJS app, only one instance will process the queued job, thanks to Redis locking mechanisms in Bull.
version: '3.8'
services:
redis:
image: redis:6.2
restart: always
ports:
- '6379:6379'
networks:
- app-network
worker:
image: node:18
working_dir: /app
volumes:
- .:/app
command: ["npm", "run", "start"]
environment:
- NODE_ENV=production
- REDIS_HOST=redis
networks:
- app-network
deploy:
replicas: 3 # Example: Run 3 instances of the worker
restart_policy:
condition: on-failure
networks:
app-network:
driver: bridge
Run:
docker-compose up -d
Now, your Redis instance is ready to use! 🚀
✅ Conclusion
Using Bull with Redis in NestJS, we’ve ensured that:
✔️ Cron jobs are scheduled only once per interval
✔️ Multiple instances don’t trigger duplicate executions
✔️ Scalability is achieved with a queue-based approach
Now you’re ready to handle scheduled tasks like a pro! 💪🔥
Happy coding! 🚀