简体   繁体   中英

NestJs - mongoose - Dynamic collection naming

I'd like to use dynamic collection names based on current year.

For example: From 'products' to 'products2020'.

Using NESTJS, I have to import "module.forFeature" with an specifyc collection name.

import { Module } from '@nestjs/common'
import { MongooseModule } from '@nestjs/mongoose'

@Module({
  imports: [
    MongooseModule.forFeature([
      {
        name: 'Products',
        schema: ProductsSchema
      }
    ])
  ],
  controllers: [ProductsController],
  providers: [ProductsService]
})

And the same happens with injection at service:

import { Injectable } from '@nestjs/common'
import { InjectModel } from '@nestjs/mongoose'

import { Model } from 'mongoose'

@Injectable()
export class ProductsService {
  constructor(
    @InjectModel('Products')
    private readonly productsModel: Model<Products>
  ) {}
}

And finally, here's my schema:

import { Schema } from 'mongoose'

export const ProductsSchema = new Schema(
  {
    _id: { Type: String, required: true },
    code: String
  },
  {
    collection: 'Products'
  }
)

Is there some way to achieve dynamic naming?

Thanks a lot !

I've looking for a solution to this kind of problem but i've hit a wall and there was no clear way to do it.

Below (minimal) code instantiate Services each bound to a specific model depending on a country parameter . ie ServiceX bound to Model of Database X, ServiceY bound to the same Model in Database Y

But here is what i managed to do. You can absolutely do a work around to fit your needs

First comes the model/interface. Commonly used between different services

export interface User extends Document {
    readonly username: string;
    readonly password: string;
}

export const UserSchema = new mongoose.Schema(
    {
        _id: mongoose.ObjectId,
        username: String,
        password: String
    },
    { collection: 'accounts', autoCreate: true }
);

Service definition is indeed the same for every model in different database/collection

@Injectable()
export class XUserService implements OnModuleInit{
    constructor(
        private userModel: Model<User>,
    ) {
    }

    ////////////////////////////////////////////////////////////////////////////
    async onModuleInit(): Promise<any> {
        console.log(`inside service dbname=: ${this.userModel.db.name} > ${this.userModel.collection.collectionName}` );
        // await new this.userModel({_id: mongoose.Types.ObjectId(), username: 'test', password: 'test', flag: this.c}).save()
    }

    async insert(){
        console.log(`inside service dbname=: ${this.userModel.db.name} > ${this.userModel.collection.collectionName}` );
        await new this.userModel({
            _id: mongoose.Types.ObjectId(),
            username: this.userModel.db.name,
            password: '0000'
        }).save();
    }

    async findOne(): Promise<User>{
        console.log(`inside service in : ${this.userModel.db.name} > ${this.userModel.collection.collectionName}` );
        return this.userModel.findOne()
    }
}

For Module, i made a DynamicModule

  • Import DBConnections
  • Create a Model for each need, ( for my case, one model in each Database )
  • Create and bind each Model to a Service , so the instantiation of the service will be correct
@Module({

})
export class XUserModule{
    static register( /*use can pass parameter here*/): DynamicModule{
        return{
            module: XUserModule,
            imports: [
                DatabaseModule
            ],
            controllers: [
                XUserController
            ],
            providers: [
                // Create Models here, #1 and #2 in two different database
                {
                    provide: 'dz-'+'UserModel',
                    useFactory: (connection: Connection)=> {
                        return connection.model('User', UserSchema )
                    },
                    inject: [ dbname.shooffood('dz')+'Connection' ]
                },{
                    provide: 'ca-'+'UserModel',
                    useFactory: (connection: Connection)=> {
                        return connection.model('User', UserSchema )
                    },
                    inject: [ dbname.shooffood('ca')+'Connection' ]
                },

   
                // Create Providers/Services for each Model and Inject the Model to the Service by `TokenString`
                {
                    provide: 'dz' + XUserService.name,
                    useFactory: (m: any)=> {
                        console.log(m);
                        return new XUserService(m);
                    },
                    inject: [ 'dz-'+'UserModel' ]
                },{
                    provide: 'ca' + XUserService.name,
                    useFactory: (m: any)=> {
                        console.log(m);
                        return new XUserService(m);
                    },
                    inject: [ 'ca-'+'UserModel' ]
                }
            ],

            // Export your service with the same `provide` name for later usage.
            exports: [
                'dz' + XUserService.name,
                'ca' + XUserService.name
            ]
        }
    }
}

Just FYI, database module looks like Constants dbname are connection names and uri are the connection string.

const databaseProviders = [
    {
        provide: dbname.admin+'Connection',
        useFactory: (): Promise<typeof mongoose> => mongoose.createConnection(uri.admin),
    },{
        provide: dbname.system+'Connection',
        useFactory: (): Promise<typeof mongoose> => mongoose.createConnection(uri.system),
    },{
        provide: dbname.shooffood('dz')+'Connection',
        useFactory: (): Promise<typeof mongoose> => mongoose.createConnection(uri.dzfood),
    },{
        provide: dbname.shooffood('ca')+'Connection',
        useFactory: (): Promise<typeof mongoose> => mongoose.createConnection(uri.cafood),
    }
];

@Module({
    providers: [
        ...databaseProviders
    ],
    exports: [
        dbname.admin+'Connection',
        dbname.system+'Connection',
        dbname.shooffood('dz')+'Connection',
        dbname.shooffood('ca')+'Connection'
    ]
})
export class DatabaseModule {}

As for Controller, there is only one that handle each service via request param :country . But first i had to list all possible Models and services to include in the Application.

@Controller(':country')
export class XUserController {
    private byCountryServices = new Map();
    constructor(
        // Inject all required services by `tokenString`
        @Inject('dz' + XUserService.name) private dzUserService: XUserService,
        @Inject('ca' + XUserService.name) private caUserService: XUserService,
    ) {
        // Add to `<key, value>` Map for easy by param access
        this.byCountryServices.set('dz', this.dzUserService );
        this.byCountryServices.set('ca', this.caUserService );
    }

    @Get('post')
    async post(
        @Param('country') c: string
    ): Promise<string>{
        await this.byCountryServices.get(c).insert()
        return 'inserted in ' + c;
    }

    @Get('get')
    async get(
        @Param('country') c: string
    ): Promise<string>{
        console.log('param: ' + c)
        return await this.byCountryServices.get(c).findOne()
    }
}

Finally you import the module in AppModule with XUserModule.register()

I stumble into a similar issue, and the way I resolved was using the MongooseModule.forFeatureAsync method. The model and schema declaration are the same as in the nestjs docs .

@Module({
  imports: [
    MongooseModule.forFeatureAsync([
      {
        name: UsersModel.name,
        imports: [EnvironmentModule],
        inject: [EnvironmentService],
        useFactory: (envService: EnvironmentService) => {
          const env = envService.getEnv();
          const schema = UsersSchema.set(
            'collection',
            `${env.countryCode}-users`,
          );
          return schema;
        },
      },
    ]),
    ...
  ],
  providers: []
  ...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM