background tasks illustration

As developers, we often come across scenarios in which we need to perform resource-intensive or time-consuming operations, like sending bulk emails or processing large amounts of data.
These operations can slow down our applications and degrade the user experience if not handled correctly. That’s where task queues come in handy.

NestJS, a Node.js framework for building efficient and scalable server-side applications, has a great solution for this problem with its built-in task scheduling and queue management feature. Using libraries like Bull, it becomes straightforward to set up and manage task queues in a NestJS application.

Let’s start by adding the necessary packages:

npm install --save @nestjs/bull bull

Next, we need to import the BullModule into our application:

import { BullModule } from '@nestjs/bull';

@Module({
  imports: [
    BullModule.forRoot({
      redis: {
        host: 'localhost',
        port: 6379,
      },
    }),
  ],
})
export class AppModule {}

In this configuration, we’re connecting to a local Redis server, which Bull uses as a message broker for managing jobs and ensuring they are processed even in cases of server crashes or restarts.

Next, let’s define a queue:

import { BullModule } from '@nestjs/bull';

@Module({
  imports: [
    BullModule.registerQueue({
      name: 'notifications',
    }),
  ],
})
export class AppModule {}

Now, let’s create a processor for our queue:

import { Processor, Process } from '@nestjs/bull';
import { Job } from 'bull';

@Processor('notifications')
export class NotificationsProcessor {
  @Process()
  async sendNotification(job: Job<unknown>) {
    const { data } = job;
    // your logic to send notifications here
  }
}

The Processor decorator tells NestJS that this class will handle jobs from the ‘notifications’ queue. The Process decorator is used to define a job handler.

Finally, let’s add jobs to our queue:

import { InjectQueue } from '@nestjs/bull';
import { Queue } from 'bull';

export class AppService {
  constructor(@InjectQueue('notifications') private notificationsQueue: Queue) {}

  async addNotification(data: unknown) {
    await this.notificationsQueue.add(data);
  }
}

In this example, whenever the addNotification method is called, a new job will be added to the ‘notifications’ queue.

Task queues can greatly improve your application’s performance and resilience, particularly for resource-intensive tasks. They allow you to schedule tasks to be processed in the background, reducing the load on your main application and providing a better user experience.