How to change a Database connection dynamically with Request Scope Providers in Nestjs? How to change a Database connection dynamically with Request Scope Providers in Nestjs? mongoose mongoose

How to change a Database connection dynamically with Request Scope Providers in Nestjs?


You can do the following using Nest's built-in Mongoose package:

/************************** mognoose.service.ts*************************/import { Inject, Injectable, Scope } from '@nestjs/common';import { MongooseOptionsFactory, MongooseModuleOptions } from '@nestjs/mongoose';import { REQUEST } from '@nestjs/core';import { Request } from '@nestjs/common';@Injectable({ scope: Scope.REQUEST })export class MongooseConfigService implements MongooseOptionsFactory {    constructor(        @Inject(REQUEST) private readonly request: Request,) {    }    createMongooseOptions(): MongooseModuleOptions {        return {            uri: request.params.uri, // Change this to whatever you want; you have full access to the request object.        };    }}/************************** mongoose.module.ts*************************/import { Module } from '@nestjs/common';import { MongooseModule } from '@nestjs/mongoose';import { MongooseConfigService } from 'mognoose.service';@Module({    imports: [        MongooseModule.forRootAsync({            useClass: MongooseConfigService,        }),    ]})export class DbModule {}

Then, you can attach whatever you want to the request and change the database per request; hence the use of the Scope.REQUEST. You can read more about Injection Scopes on their docs.


Edit: If you run into issues with PassportJS (or any other package) or the request is empty, it seems to be an error that relates to PassportJS (or the other package) not supporting request scopes; you may read more about the issue on GitHub regarding PassportJS.


I did a simple implementation for nest-mongodb,

The main changes are in mongo-core.module.ts where I store the connections in a map and used them if available instead of creating a new connection every time.

import {    Module,    Inject,    Global,    DynamicModule,    Provider,    OnModuleDestroy,} from '@nestjs/common';import { ModuleRef } from '@nestjs/core';import { MongoClient, MongoClientOptions } from 'mongodb';import {    DEFAULT_MONGO_CLIENT_OPTIONS,    MONGO_MODULE_OPTIONS,    DEFAULT_MONGO_CONTAINER_NAME,    MONGO_CONTAINER_NAME,} from './mongo.constants';import {    MongoModuleAsyncOptions,    MongoOptionsFactory,    MongoModuleOptions,} from './interfaces';import { getClientToken, getContainerToken, getDbToken } from './mongo.util';import * as hash from 'object-hash';@Global()@Module({})export class MongoCoreModule implements OnModuleDestroy {    constructor(        @Inject(MONGO_CONTAINER_NAME) private readonly containerName: string,        private readonly moduleRef: ModuleRef,    ) {}    static forRoot(        uri: string,        dbName: string,        clientOptions: MongoClientOptions = DEFAULT_MONGO_CLIENT_OPTIONS,        containerName: string = DEFAULT_MONGO_CONTAINER_NAME,    ): DynamicModule {        const containerNameProvider = {            provide: MONGO_CONTAINER_NAME,            useValue: containerName,        };        const connectionContainerProvider = {            provide: getContainerToken(containerName),            useFactory: () => new Map<any, MongoClient>(),        };        const clientProvider = {            provide: getClientToken(containerName),            useFactory: async (connections: Map<any, MongoClient>) => {                const key = hash.sha1({                    uri: uri,                    clientOptions: clientOptions,                });                if (connections.has(key)) {                    return connections.get(key);                }                const client = new MongoClient(uri, clientOptions);                connections.set(key, client);                return await client.connect();            },            inject: [getContainerToken(containerName)],        };        const dbProvider = {            provide: getDbToken(containerName),            useFactory: (client: MongoClient) => client.db(dbName),            inject: [getClientToken(containerName)],        };        return {            module: MongoCoreModule,            providers: [                containerNameProvider,                connectionContainerProvider,                clientProvider,                dbProvider,            ],            exports: [clientProvider, dbProvider],        };    }    static forRootAsync(options: MongoModuleAsyncOptions): DynamicModule {        const mongoContainerName =            options.containerName || DEFAULT_MONGO_CONTAINER_NAME;        const containerNameProvider = {            provide: MONGO_CONTAINER_NAME,            useValue: mongoContainerName,        };        const connectionContainerProvider = {            provide: getContainerToken(mongoContainerName),            useFactory: () => new Map<any, MongoClient>(),        };        const clientProvider = {            provide: getClientToken(mongoContainerName),            useFactory: async (                connections: Map<any, MongoClient>,                mongoModuleOptions: MongoModuleOptions,            ) => {                const { uri, clientOptions } = mongoModuleOptions;                const key = hash.sha1({                    uri: uri,                    clientOptions: clientOptions,                });                if (connections.has(key)) {                    return connections.get(key);                }                const client = new MongoClient(                    uri,                    clientOptions || DEFAULT_MONGO_CLIENT_OPTIONS,                );                connections.set(key, client);                return await client.connect();            },            inject: [getContainerToken(mongoContainerName), MONGO_MODULE_OPTIONS],        };        const dbProvider = {            provide: getDbToken(mongoContainerName),            useFactory: (                mongoModuleOptions: MongoModuleOptions,                client: MongoClient,            ) => client.db(mongoModuleOptions.dbName),            inject: [MONGO_MODULE_OPTIONS, getClientToken(mongoContainerName)],        };        const asyncProviders = this.createAsyncProviders(options);        return {            module: MongoCoreModule,            imports: options.imports,            providers: [                ...asyncProviders,                clientProvider,                dbProvider,                containerNameProvider,                connectionContainerProvider,            ],            exports: [clientProvider, dbProvider],        };    }    async onModuleDestroy() {        const clientsMap: Map<any, MongoClient> = this.moduleRef.get<            Map<any, MongoClient>        >(getContainerToken(this.containerName));        if (clientsMap) {            await Promise.all(                [...clientsMap.values()].map(connection => connection.close()),            );        }    }    private static createAsyncProviders(        options: MongoModuleAsyncOptions,    ): Provider[] {        if (options.useExisting || options.useFactory) {            return [this.createAsyncOptionsProvider(options)];        } else if (options.useClass) {            return [                this.createAsyncOptionsProvider(options),                {                    provide: options.useClass,                    useClass: options.useClass,                },            ];        } else {            return [];        }    }    private static createAsyncOptionsProvider(        options: MongoModuleAsyncOptions,    ): Provider {        if (options.useFactory) {            return {                provide: MONGO_MODULE_OPTIONS,                useFactory: options.useFactory,                inject: options.inject || [],            };        } else if (options.useExisting) {            return {                provide: MONGO_MODULE_OPTIONS,                useFactory: async (optionsFactory: MongoOptionsFactory) =>                    await optionsFactory.createMongoOptions(),                inject: [options.useExisting],            };        } else if (options.useClass) {            return {                provide: MONGO_MODULE_OPTIONS,                useFactory: async (optionsFactory: MongoOptionsFactory) =>                    await optionsFactory.createMongoOptions(),                inject: [options.useClass],            };        } else {            throw new Error('Invalid MongoModule options');        }    }}

Check out the full implementation