NestJS BullMQ Background Jobs Tutorial
Why Background Jobs Matter for SaaS
Every production SaaS application needs background processing. Whether it is sending emails, generating reports, processing uploads, or syncing third-party data, you cannot block your API responses waiting for slow operations. BullMQ paired with NestJS gives you a battle-tested solution backed by Redis.
Setting Up BullMQ in NestJS
First, install the required packages:
npm install @nestjs/bullmq bullmq
Register the BullMQ module in your app:
import { BullModule } from '@nestjs/bullmq';
@Module({
imports: [
BullModule.forRoot({
connection: {
host: 'localhost',
port: 6379,
},
}),
BullModule.registerQueue({
name: 'email',
}),
],
})
export class AppModule {}
Creating a Producer
Inject the queue into any service to add jobs:
@Injectable()
export class EmailService {
constructor(@InjectQueue('email') private emailQueue: Queue) {}
async sendWelcomeEmail(userId: string) {
await this.emailQueue.add('welcome', { userId }, {
attempts: 3,
backoff: { type: 'exponential', delay: 2000 },
removeOnComplete: 100,
});
}
}
Cloudrix SaaS Starter ships with this pre-configured and tested.
Skip weeks of boilerplate — auth, payments, multi-tenancy, and deployment included out of the box.
Try the live demo →The attempts and backoff options mean BullMQ will automatically retry failed jobs with exponential delay. This is critical for unreliable external services like email providers.
Creating a Consumer
Processors handle jobs from the queue:
@Processor('email')
export class EmailProcessor extends WorkerHost {
async process(job: Job<{ userId: string }>) {
const { userId } = job.data;
const user = await this.userService.findById(userId);
await this.mailer.send({
to: user.email,
subject: 'Welcome!',
template: 'welcome',
});
}
}
Job Priority and Scheduling
BullMQ supports priority queues and delayed jobs out of the box:
// High-priority job
await this.queue.add('critical-alert', data, { priority: 1 });
// Scheduled job — runs in 1 hour
await this.queue.add('reminder', data, { delay: 3600000 });
// Recurring job — runs every day at midnight
await this.queue.upsertJobScheduler('daily-report', {
pattern: '0 0 * * *',
}, { name: 'generate-report', data: {} });
Monitoring with Bull Board
Add a dashboard to monitor your queues in real time:
import { BullBoardModule } from '@bull-board/nestjs';
import { BullMQAdapter } from '@bull-board/api/bullMQAdapter';
BullBoardModule.forFeature({ name: 'email', adapter: BullMQAdapter });
This gives you visibility into completed, failed, delayed, and active jobs without any custom tooling.
Multi-Tenant Job Isolation
In a SaaS context, you want to ensure one tenant's heavy workload does not starve others. Use named queues per job type and set concurrency limits:
@Processor('email', { concurrency: 5 })
export class EmailProcessor extends WorkerHost { ... }
You can also add tenant IDs to job data and implement fair scheduling by checking active job counts per tenant before adding new ones.
Error Handling Best Practices
Always implement a failed-job handler so issues are visible:
@OnWorkerEvent('failed')
onFailed(job: Job, error: Error) {
this.logger.error(`Job ${job.id} failed: ${error.message}`);
this.alertService.notify('job-failed', { jobId: job.id, error });
}
Get Started Today
All of these patterns are fully implemented in SaaS Starter. Skip 2-6 weeks of setup.
- Free version: GitHub
- Pro ($249): Get SaaS Starter Pro