Search code examples
javascriptdependency-injectionnestjsnestjs-config

NestJs Module dependency issue


I am working on a NestJs application where I have created a helper class and I am using a function from a module's class inside that helper class. but I am getting some errors. First I was getting undefined error then I made the helper class injectable then I am facing dependencies issues again.

This is the error I am facing currently.

Error: Nest can't resolve dependencies of the BoomiService (?). Please make sure that the argument dependency at index [0] is available in the CronjobsModule context.

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

at Injector.resolveSingleParam (D:\WorkData\AGData\JavascriptProjects\orion\orion-gateway-api\node_modules\@nestjs\core\injector\injector.js:178:19)
at resolveParam (D:\WorkData\AGData\JavascriptProjects\orion\orion-gateway-api\node_modules\@nestjs\core\injector\injector.js:116:49)
at Array.map (<anonymous>)
at Injector.resolveConstructorParams (D:\WorkData\AGData\JavascriptProjects\orion\orion-gateway-api\node_modules\@nestjs\core\injector\injector.js:131:58)
at Injector.loadInstance (D:\WorkData\AGData\JavascriptProjects\orion\orion-gateway-api\node_modules\@nestjs\core\injector\injector.js:57:24)
at Injector.loadProvider (D:\WorkData\AGData\JavascriptProjects\orion\orion-gateway-api\node_modules\@nestjs\core\injector\injector.js:84:20)
at D:\WorkData\AGData\JavascriptProjects\orion\orion-gateway-api\node_modules\@nestjs\core\injector\instance-loader.js:47:62
at Array.map (<anonymous>)
at InstanceLoader.createInstancesOfProviders (D:\WorkData\AGData\JavascriptProjects\orion\orion-gateway-api\node_modules\@nestjs\core\injector\instance-loader.js:47:36)
at D:\WorkData\AGData\JavascriptProjects\orion\orion-gateway-api\node_modules\@nestjs\core\injector\instance-loader.js:32:24
at Array.map (<anonymous>)
at InstanceLoader.createInstances (D:\WorkData\AGData\JavascriptProjects\orion\orion-gateway-api\node_modules\@nestjs\core\injector\instance-loader.js:31:49)
at InstanceLoader.createInstancesOfDependencies (D:\WorkData\AGData\JavascriptProjects\orion\orion-gateway-api\node_modules\@nestjs\core\injector\instance-loader.js:21:20)
at D:\WorkData\AGData\JavascriptProjects\orion\orion-gateway-api\node_modules\@nestjs\core\nest-factory.js:96:38
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at Function.asyncRun (D:\WorkData\AGData\JavascriptProjects\orion\orion-gateway-api\node_modules\@nestjs\core\errors\exceptions-zone.js:22:13)
at NestFactoryStatic.initialize (D:\WorkData\AGData\JavascriptProjects\orion\orion-gateway-api\node_modules\@nestjs\core\nest-factory.js:94:13)
at NestFactoryStatic.create (D:\WorkData\AGData\JavascriptProjects\orion\orion-gateway-api\node_modules\@nestjs\core\nest-factory.js:37:9)
at bootstrap (D:\WorkData\AGData\JavascriptProjects\orion\orion-gateway-api\src\main.ts:9:15)

This is my helper class.

                import axios from "axios";
            import * as qs from'querystring';
            import { OpenApiService } from "../open-api/open-api.service";
            import {
                BadRequestException,
                Injectable,
                NotFoundException,
              } from '@nestjs/common';
            @Injectable()
            export class BoomiService {
                constructor(private OpenApiService: OpenApiService) {}
                //get services
                // 
                //Post services
                createRenewalRequest = async (data) => {
                    const config = {
                        headers: {
                            'Content-Type': 'application/json',
                            "x-api-key": process.env.BOOMI_APIKEY
                        }
                    };
                    // console.log('data',data)
                    try {
                        // console.log('here in createQRFeedbackForComm',config);
                        return axios.post(
                            `${process.env.BOOMI_API_BASEURL}${process.env.BOOMI_POST_APIENDPOINT}`,
                            JSON.stringify(data),
                            config
                        ).then(async (response) => {
                            const boomilogs = await this.OpenApiService.saveOpenApiLogs({reason:response.data,reqData:data,type:'CXMRenewalRequest',status:'Completed'});
                            return response.data;
                            }).catch(async (e) => {
                                const boomilogs = await this.OpenApiService.saveOpenApiLogs({reason:e,reqData:data,type:'CXMRenewalRequest',status:'Failed'});
                            return (`error in createRenewalRequest => ${e}`);
                        });
                        
                    } catch (e) {
                        const boomilogs = await this.OpenApiService.saveOpenApiLogs({reason:e,reqData:data,type:'CXMRenewalRequest',status:'Completed'});
                        throw e;
                    }
                }
                createMaintainenceRequest = async (data) => {
                    const config = {
                        headers: {
                            'Content-Type': 'application/json',
                            "x-api-key": process.env.BOOMI_APIKEY
                        }
                    };
                    try {
                        console.log('response->',this.OpenApiService)
                        // console.log('here in createQRFeedbackForComm',config);
                        return axios.post(
                            `${process.env.BOOMI_API_BASEURL}${process.env.BOOMI_POST_APIENDPOINT}`,
                            JSON.stringify(data),
                            config
                        ).then(async (response) => {
                            const boomilogs = await this.OpenApiService.saveOpenApiLogs({reason:response.data,reqData:data,type:'CXMMaintainenceRequest',status:'Completed'});
                            return response.data;
                        }).catch(async (e) => {
                            console.log('response=',e)
                            const boomilogs = await this.OpenApiService.saveOpenApiLogs({reason:e,reqData:data,type:'CXMMaintainenceRequest',status:'Failed'});
                            return (`error in createMaintainenceRequest => ${e}`);
                        });
                    } catch (e) {
                        const boomilogs = await this.OpenApiService.saveOpenApiLogs({reason:e,reqData:data,type:'CXMMaintainenceRequest',status:'Failed'});
                        throw e;
                    }
                }
            }

here I am using a method saveOpenApiLogs from the OpenApiService.

This is my Open api service file.

                import {
              BadRequestException,
              Injectable,
              NotFoundException,
            } from '@nestjs/common';
            import { HttpService } from '@nestjs/axios';
            import * as moment from 'moment';
            import constants from '../config/constant';
            import { CreateOpenApiDto } from './dto/create-open-api.dto';
            import { UpdateOpenApiDto } from './dto/update-open-api.dto';
            import { empty, firstValueFrom } from 'rxjs';
            import { error } from 'console';
            import { PropertyDto } from './dto/property.dto';
            import utils from 'src/utils/utils';
            import { util } from 'prettier';
            import { CCAService } from 'src/utils/ccaService';
            import {BoomiService} from 'src/utils/BoomiService';
            import { v4 as uuidv4 } from "uuid";
            var {
              communityList,
              nationalityList,
              leaseNatureList,
              leaseTypeList,
              paymentScheduleArrName,
              paymentStatusForAGP,
              bhoomi_x_api_key,
            } = constants;
            const numberWithDastPattern = /^(\-?\d+)$/;
            const mobileNumberPattern = /^(\+?\d+)$/;

            @Injectable()
            export class OpenApiService {
              constructor(
                private ccaService: CCAService,
                private BoomiService: BoomiService,
                private httpService: HttpService,
              ) {}
              propertyAPI = process.env.PROPERTY_API;
              userAPI = process.env.USER_API;
              maintenanceAPI = process.env.MAINTENANCE_API;
              leaseAPI = process.env.LEASING_API;
              
              async sendMaintainenceNotificationToBoomi() {
                
                  let DataResponse = {},boomiResponse = {}
                  try {
                    const url = `${this.maintenanceAPI}ticket`;
                    let params: any = {};
                    params.todayTickets=  true;
                    params.perPage=  100;
                    params.ticketStatus=  'issue_resolved,ag_not_responsible,landlord_not_responsible,reject';
                      let ticketData:any = await this.httpService
                      .get(
                        url, {params},
                      )
                      .toPromise()
                      .catch((e) => {
                        console.log('Error in todayTickets',e);
                        throw new Error(e.response.data.error);
                      });
                      // console.log('ticketData',ticketData.data.result.data);
                    if (ticketData && ticketData.data.result.data && ticketData.data.success) {
                      let tickets = ticketData.data.result.data;
                      let dataForBoomi:any[] =  []
                      console.log('tickets -> length',tickets.length)
                      if(tickets.length>0){
                        await Promise.all(tickets.map(async (ticket) =>{
                          // console.log('ticket',ticket);
                          const url = `${this.userAPI}user/detail`;
                          const unit_url = `${this.propertyAPI}unit?unitId=${ticket?.unitId}&status=enable&select=propertyName,unit.unitNumber,address.community,address.subCommunity`;
                            let Tenant: any = await firstValueFrom(
                              this.httpService.post(url, {user:ticket?.tenantId}),
                            ).catch((e) => {
                              console.log('ERROR---', e);
                              throw new Error(e.response.data.error);
                            });
                            // console.log('Tenant',Tenant?.data?.result)
                            let unit:any = await this.httpService
                            .get(
                              unit_url,
                            )
                            .toPromise()
                            .catch((e) => {
                              console.log('Error in get unit',e);
                              throw new Error(e.response.data.error);
                            });
                            // console.log('unit',unit?.data?.result.data[0])
                          dataForBoomi.push(
                            {
                              "firstName": Tenant?.data?.result?.name||"",
                              "lastName":  Tenant?.data?.result?.name||"",
                              "fullName":  Tenant?.data?.result?.name||"",
                              "emailId": Tenant?.data?.result?.email||"",
                              "whatsAppPhoneNumber": Tenant?.data?.result?.mobile||"",
                              "propertyType": "Residential",
                              "unitLeased": unit?.data?.result?.data[0]?.unit?.unitNumber||"",
                              "propertyName": unit?.data?.result?.data[0]?.propertyName||"",
                              "location": unit?.data?.result?.data[0]?.address?.community||"",
                              "subLocation": unit?.data?.result?.data[0]?.address?.subCommunity||"",
                              "communicationType": "Email",
                              "complaintType":"MAINTENANCE_REQUEST",
                              "complaintSubType": ticket?.ratingAndReview?.rating||'',
                              "businessUnit": "Properties",
                              "eventType": "MAINTENANCE_REQUEST",
                              "transactionDate": ticket?.updatedAt,
                              "categoryOfMaintenance": ticket?.category?.name||'',
                              "technicianAssigned": ticket?.technician?.name||'',
                              "attachmentURL": "",
                              "userType": "",
                              "comments": ticket?.ratingAndReview?.comment||''
                            }
                          );
                          // console.log('dataForBoomi',dataForBoomi)
                          return dataForBoomi
                        })).then(async (dataForBoomi) => {
                          // console.log('dataForBoomi',dataForBoomi)
                            let PayloadForBoomi = {
                              "QRCodeFeedback": dataForBoomi[0]
                            }
                            // console.log('PayloadForBoomi',PayloadForBoomi)
                            boomiResponse = await this.BoomiService.createMaintainenceRequest(PayloadForBoomi)
                            // console.log('boomiResponse:',boomiResponse)
                          console.log('------boomiResponse-----', boomiResponse);
                          DataResponse = boomiResponse;
                            return DataResponse
                          })
                          .catch(error => {
                            console.error('Error:', error);
                          });
                      }          
                    }
                  } catch (error) {
                    DataResponse = error;
                  }
                  return {
                    DataResponse
                  };
                }

                async saveOpenApiLogs(reqBody=null){
                  console.log('reqBody',reqBody)
                  return await firstValueFrom(
                    this.httpService.post(`${this.userAPI}openAPItenantStatus`, {
                      uuid: uuidv4(),
                      reason: reqBody?.reason||'',
                      request: reqBody?.reqData?JSON.stringify(reqBody.reqData):'',
                      type: reqBody?.type||'',
                      status: reqBody?.status||'pending',
                    }),
                  ).catch((e) => {return e});
                }
            }

I have removed irrelevant methods from this class else it would be a messy code here.

this is my open api module file where I have imported helper class.

        import { Module } from '@nestjs/common';
        import { OpenApiService } from './open-api.service';
        import { OpenApiController } from './open-api.controller';
        import { HttpModule } from '@nestjs/axios';
        import { CCAService } from 'src/utils/ccaService';
        import { BoomiService } from 'src/utils/BoomiService';
        import { BookingModule } from 'src/booking/booking.module';
        import { CronjobsModule } from 'src/cronjobs/cronjobs.module';

        @Module({
          controllers: [OpenApiController],
          providers: [OpenApiService,CCAService,BoomiService],
          imports: [HttpModule,BookingModule,CronjobsModule],
        })
        export class OpenApiModule {}

This is the module file for which I am getting the error

        import { Module } from '@nestjs/common';
    import { CronjobsService } from './cronjobs.service';
    import { HttpModule } from '@nestjs/axios';
    import { NotificationModule } from 'src/notification/notification.module';
    import { MaintenanceService } from 'src/maintenance/maintenance.service';
    import { OpenApiService } from 'src/open-api/open-api.service';
    import { CCAService } from 'src/utils/ccaService';
    import { BoomiService } from 'src/utils/BoomiService';
    import { BookingModule } from 'src/booking/booking.module';

    @Module({
      imports:[HttpModule, NotificationModule,BookingModule],
      providers: [CronjobsService, MaintenanceService,OpenApiService,CCAService,BoomiService]
    })
    export class CronjobsModule {}

I am new to NestJs and unable to find out the issue. has anyone else faced the same? Please guide the solution.

Thanks


Solution

  • This is because you have a circular dependency. Which means a module is being imported\injected in a loop.

    In your case:

    • in OpenApiService you are importing 'BoomiService`.
    • also in BoomiService, you are importing OpenApiService.

    Whenever runtime is initializing OpenApiService, it calls Boomi, then boomi calls openAi and so in.

    A circular dependency is usually an architectural decision error from your end.

    I noticed in your openAiService, you are using only one method from BoomiService

    boomiResponse = await this.BoomiService.createMaintainenceRequest(PayloadForBoomi)
    

    It would be easier to extract this and move it somewhere else. But also commenting this line, and removing the import of boomiservice in your openaiService, should resolve the error and have your nest project boot up. This is just to validate a proof of concept.

    What should be done, is perhaps create a third service, which imports both Boomi and OpenAi, and there, create a function that calls what you want, and when errors are thrown, log them.

    It would also make your code cleaner, and easier to step through, instead of chaining async functions and using the returns in .then().

    It is also discussed in the NestJs docs.