node.jstypescriptnestjsbackendbullmq

Why is NestJs not able to resolve dependencies


I am trying to integrate bullmq in NestJs project. But facing the following error:

Error: Nest can't resolve dependencies of the CustomerSubscriptionPaymentsService (CustomerSubscriptionPaymentsRepo, ?). Please make sure that the argument "BullQueue_invoice" at index [1] is available in the CustomerSubscriptionPaymentsModule context.

Potential solutions:
- Is CustomerSubscriptionPaymentsModule a valid NestJS module?
- If "BullQueue_invoice" is a provider, is it part of the current CustomerSubscriptionPaymentsModule?
- If "BullQueue_invoice" is exported from a separate @Module, is that module imported within CustomerSubscriptionPaymentsModule?
  @Module({
    imports: [ /* the Module containing "BullQueue_invoice" */ ]
  })

Here is the project directory:

modules
  app
    app.module.ts
    app.controller.ts    
  customerSubscriptionPayments
    customerSubscriptionPayments.module.ts
    customerSubscriptionPayments.service.ts
    customerSubscriptionPayments.controller.ts
  invoice
    invouce.module.ts
    invoice.controller.ts
    invoice.service.ts
    invoice.processor.ts

app.module.ts

import { Module } from '@nestjs/common';
import { BullModule } from '@nestjs/bullmq';
import { AppController } from './app.controller';
import { CustomerSubscriptionPaymentsModule } from '../customerSubscriptionPayments/customerSubscriptionPayments.module';
import { CustomerSubscriptionPaymentsService } from '../customerSubscriptionPayments/customerSubscriptionPayments.service';

@Module({
  imports: [
    BullModule.forRoot({
      connection: {
        host: 'localhost',
        port: 6379,
      },
    }),
    CustomerSubscriptionPaymentsModule,
  ],
  providers: [CustomerSubscriptionPaymentsService],
  controllers: [AppController],
})
export class AppModule {}

invoice.module.ts

import { Module } from '@nestjs/common';
import { BullModule } from '@nestjs/bullmq';
import { InvoiceController } from './invoice.controller';
import { InvoiceService } from './invoice.service';
import { InvoiceRepo } from './invoice.repo';
import { RedisQueues } from 'src/constants';
import { InvoiceProcessor } from './invoice.processor';

@Module({
  imports: [
    BullModule.registerQueue({
      name: 'invoice',
    }),
  ],
  controllers: [InvoiceController],
  providers: [InvoiceService, InvoiceRepo],
  exports: [InvoiceService, InvoiceRepo],
})
export class InvoiceModule {}

invoice.processor.ts

import { Processor, WorkerHost } from '@nestjs/bullmq';
import { Job } from 'bullmq';
import { InvoiceQueueTasks } from './constants';

@Processor('invoice')
export class InvoiceProcessor extends WorkerHost {
  async generateInvoice(job: Job) {
    const { data } = job;

    if (data.customerSubscriptionId) {
      console.error('customerSubscriptionId not found');
      return {};
    }

    //TODO: Implement invoice generation logic
  }

  async process(job: Job) {
    const { data, name } = job;

    if (name === InvoiceQueueTasks.GENERATE) {
      return this.generateInvoice(job);
    }

    return {};
  }
}

customerSubscriptionPayments.module.ts

import { Module } from '@nestjs/common';
import { CustomerSubscriptionPaymentsController } from './customerSubscriptionPayments.controller';
import { CustomerSubscriptionPaymentsRepo } from './customerSubscriptionPayments.repo';
import { CustomerSubscriptionPaymentsService } from './customerSubscriptionPayments.service';

@Module({
  controllers: [CustomerSubscriptionPaymentsController],
  providers: [
    CustomerSubscriptionPaymentsService,
    CustomerSubscriptionPaymentsRepo,
  ],
  exports: [
    CustomerSubscriptionPaymentsService,
    CustomerSubscriptionPaymentsRepo,
  ],
})
export class CustomerSubscriptionPaymentsModule {}

customerSubscriptionPayments.service.ts

import { InjectQueue } from '@nestjs/bullmq';
import { Queue } from 'bullmq';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';

@Injectable()
export class CustomerSubscriptionPaymentsService {
  constructor(@InjectQueue('invoice') private invoiceQueue: Queue) {}

  async generateInvoice() {
    const job = await this.invoiceQueue.add('generate', {
      customerSubscriptionId: 1,
    });

    if (!job) {
      throw new HttpException(
        'Failed to generate invoice',
        HttpStatus.INTERNAL_SERVER_ERROR,
      );
    }

    return {
      success: true,
    };
  }
}

Solution

  • The problem is that you are not importing your Bull Queue into your customerSubscriptionPayments Module, that's why when you try to inject it in the customerSubscriptionPayments Service it won't be able to resolve the dependency.

    @Module({
      imports: [
        BullModule.registerQueue({
          name: 'invoice',
        }),
      ]
      controllers: [CustomerSubscriptionPaymentsController],
      providers: [
        CustomerSubscriptionPaymentsService,
        CustomerSubscriptionPaymentsRepo,
      ],
      exports: [
        CustomerSubscriptionPaymentsService,
        CustomerSubscriptionPaymentsRepo,
      ],
    })
    

    Another way of doing things

    Since in NestJS docs there's no documentation that shows how to export a registered Bull Queue you could create a Module that exports a Service that handles the queue independently, after that you can import that module into other services so you can add, delete or do any queue related action within any service you need to. You could do this in your Invoice Module since that's the place you were registering the queue in the first place.

    Example:

    Invoice Module

    @Module({
      imports: [
        BullModule.registerQueue({
          name: 'invoice',
        }),
      ],
      controllers: [InvoiceController],
      providers: [InvoiceService, InvoiceRepo, InvoiceQueueHandler],
      exports: [InvoiceService, InvoiceRepo, InvoiceQueueHandler],
    })
    export class InvoiceModule {}
    

    Invoice Queue Handler

    export class InvoiceQueueHandler(){
     constructor(InjectQueue('invoice') private invoiceQueue: Queue){}
    
     addJob(){
      // perform some action 
     }
    
     // rest of your functions to handle the queue
    }
    

    customerSubscriptionPayments Module

    @Module({
      import: [InvoiceModule]
      controllers: [CustomerSubscriptionPaymentsController],
      providers: [
        CustomerSubscriptionPaymentsService,
        CustomerSubscriptionPaymentsRepo,
      ],
      exports: [
        CustomerSubscriptionPaymentsService,
        CustomerSubscriptionPaymentsRepo,
      ],
    })
    export class CustomerSubscriptionPaymentsModule {}
    

    customerSubscriptionPayments Module

    @Injectable()
    export class CustomerSubscriptionPaymentsService {
      constructor(private invoiceQueueHandler: InvoiceQueueHandler) {}
      
      someFunction(){
       this.invoiceQueueHandler.addJob()
      }
    }