Home > front end >  Nest can't resolve dependencies: multiple database connections using mongoose package
Nest can't resolve dependencies: multiple database connections using mongoose package

Time:09-01

Hi I am trying to set up multiple database connections using mongoose nestjs package with named connections and following along the documentation found here (https://docs.nestjs.com/techniques/mongodb#multiple-databases): but I am getting a runtime error on startup:

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

This only happens when I use named connections. If I remove the connection name 'partners' from forFeature parameter (even though keeping it at forRootAsync), it is working fine. Probably because the model connects to the default connection and since there is only one, it automatically connects with 'partners'.

// dependencies
    "@nestjs/axios": "^0.0.8",
    "@nestjs/common": "^8.4.5",
    "@nestjs/config": "^1.0.1",
    "@nestjs/core": "^8.4.5",
    "@nestjs/mongoose": "^8.0.1",
@Module({
  imports: [DatabaseModule, RetailPartnersModule],
})
export class AppModule {}
@Module({
  imports: [ConfigModule, MongooseModule.forRootAsync(B2CPartnersDbAsyncOptions)],
  providers: [DatabaseService],
  exports: [DatabaseService],
})
export class DatabaseModule {}
export const B2CPartnersDbAsyncOptions = {
  imports: [ConfigModule],
  useFactory: (configService: ConfigService) => {
    const user = configService.get<string>(MONGODB_USERNAME)
    const password = configService.get<string>(MONGODB_PASSWORD)
    const database = configService.get<string>('b2c_partners_acc')
    const host = configService.get<string>(MONGODB_URL)
    const uri = `mongodb srv://${user}:${password}@${host}/${database}`

    return {
      uri,
      useNewUrlParser: true,
      useUnifiedTopology: true,
      connectionName: 'partners',
    }
  },
  inject: [ConfigService],
}
@Module({
  imports: [
    MongooseModule.forFeature(
      [{ name: RetailLocationModel.name, schema: RetailLocationSchema }],
      'partners'
    ),
  ],
  controllers: [RetailPartnersController],
  providers: [RetailPartnersService, RetailLocationsRepository],
})
export class RetailPartnersModule {}
export class RetailLocationsRepository {
  constructor(
    @InjectModel(RetailLocationModel.name) private model: Model<RetailLocationDocument>
  ) {}
}

Note that I cannot add 'partners' as second parameter in InjectModel, as TS complains it only expects 1 argument (even though official docs say that I can pass the connection name as extra argument. When manually updating typings to support 2 parameters, I still get the same runtime error of unresolved dependencies

Update:

When I go into the mongoose module provided by the package and log the result of the static methods forRootAsync and forFeature, forRootAsync does not provide the partnersConnection token, where forFeature is trying to inject it

// Mongoose.module.ts

class MongooseModule {
    static forRootAsync(options) {
        return {
            module: MongooseModule_1,
            imports: [mongoose_core_module_1.MongooseCoreModule.forRootAsync(options)],
        };
    }
}

where logging forRootAsync.imports[0].providers yields:

    {
    provide: 'MongooseModuleOptions',
    useFactory: [Function (anonymous)],
    inject: [ [class ConfigService] ]
  },
  {
    provide: 'DatabaseConnection',
    useFactory: [Function: useFactory],
    inject: [ 'MongooseModuleOptions' ]
  },
  { provide: 'MongooseConnectionName', useValue: 'DatabaseConnection' }

And with forFeature:

    static forFeature(models = [], connectionName) {
        const providers = mongoose_providers_1.createMongooseProviders(connectionName, models);
        const result =
         {
            module: MongooseModule_1,
            providers: providers,
            exports: providers,
        };
        console.log('result forFeature1: ', result.providers)
        return result;
    }

logs to:

[{
    provide: 'RetailLocationModelModel',
    useFactory: [Function: useFactory],
    inject: [ 'partnersConnection' ]
  }]

So it seems that the partnersConnection token is not being set properly in the forRootAsync static function, as the connection is named to the default value of 'DatabaseConnection'

I verified this by changing the connectionName of the RetailLocationsModule to 'Database' and the runtime error is resolved.

@Module({
  imports: [
    MongooseModule.forFeature(
      [{ name: RetailLocationModel.name, schema: RetailLocationSchema }],
      'Database'
    ),
  ],
  controllers: [RetailPartnersController],
  providers: [RetailPartnersService, RetailLocationsRepository],
})
export class RetailPartnersModule {}

Therefore either there is a bug in forRootAsync or I am missing something.

CodePudding user response:

Instead of supplying connectionName to the factory, supply it to the options object of MongooseAsyncOptions:

export function createDbConfig(
  dbName: string
): (configService: ConfigService) => MongooseModuleAsyncOptions {
  return (configService: ConfigService): MongooseModuleOptions => {
    const logger = new Logger(createDbConfig.name)
    const user = configService.get<string>(MONGODB_USERNAME, '')
    const password = configService.get<string>(MONGODB_PASSWORD, '')
    const database = configService.get<string>(dbName, '')
    const host = configService.get<string>(MONGODB_URL, '')
    const mongoProtocol =
      configService.get<string>(NODE_ENV) === 'local' ? Protocols.local : Protocols.production
    const uri = `${mongoProtocol}://${user}:${password}@${host}/${database}`

    logger.verbose(`Connecting to the Mongo database URI: ${uri}`)

    return {
      uri,
      useNewUrlParser: true,
      useUnifiedTopology: true,
      retryAttempts: 0,
      // connectionName: 'partners' <= remove from here
    }
  }
}

export const B2CPartnersDbAsyncOptions: MongooseModuleAsyncOptions = {
  imports: [ConfigModule],
  useFactory: createDbConfig(DB_NAME.default),
  inject: [ConfigService],
  connectionName: 'partners', // <= put it here instead
}

Then in the module where you use @InjectConnection, supply the name of the connection ('partners'), as well as with every MongooseModule.forFeature, e.g.

@Injectable()
export class DatabaseService {
  constructor(@InjectConnection('partners') private _connection: Connection) {}

  public getStatus(): DatabaseHealthStatus {
    return this._connection && this._connection.readyState === 1
      ? DatabaseHealthStatus.CONNECTED
      : DatabaseHealthStatus.DISCONNECTED
  }

  public get connection(): Connection {
    return this._connection
  }
}
@Module({
  imports: [
    MongooseModule.forFeature(
      [{ name: RetailLocationModel.name, schema: RetailLocationSchema }],
      'partners'
    ),
  ],
  controllers: [RetailPartnersController],
  providers: [RetailPartnersService, RetailLocationsRepository],
})
export class RetailPartnersModule {}
  • Related