nestjs/mongoose

When set connectionName to useClass, Populate Error: Schema hasn't been registered for model

Closed this issue · 1 comments

Is there an existing issue for this?

  • I have searched the existing issues

Current behavior

App Module

import { Module } from '@nestjs/common';
import { MasterModule } from './master/master.module';
import { CoreModule } from './core/core.module';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { MongooseModule } from '@nestjs/mongoose';
import { MasterConnection } from './config/master.connection';
import { ConnectionName } from './utils/connectionn.enum';
import { CoreConnection } from './config/core.connection';

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true }),
    MongooseModule.forRootAsync({
      useClass: MasterConnection,
      connectionName: ConnectionName.MASTER,
    }),
    MongooseModule.forRootAsync({
      useClass: CoreConnection,
      connectionName: ConnectionName.CORE,
    }),
    MasterModule,
    CoreModule,
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}

Master Config Service

import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import {
  MongooseModuleOptions,
  MongooseOptionsFactory,
} from '@nestjs/mongoose';

@Injectable()
export class MasterConnection implements MongooseOptionsFactory {
  constructor(private configService: ConfigService) {}
  createMongooseOptions(): MongooseModuleOptions {
    return {
      uri: this.configService.getOrThrow('MONGODB_URI'),
      dbName: this.configService.getOrThrow('MASTER_DB'),
      connectionFactory(connection: any, name: any) {
        connection.plugin(require('mongoose-autopopulate'));
        return connection;
      },
    };
  }
}

Core Config Service

import { Inject, Injectable, NotFoundException, Scope } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { REQUEST } from '@nestjs/core';
import { Request } from 'express';
import {
  MongooseModuleOptions,
  MongooseOptionsFactory,
} from '@nestjs/mongoose';
import { TenantService } from 'src/master/tenant/services/tenant.service';

@Injectable({ scope: Scope.REQUEST })
export class CoreConnection implements MongooseOptionsFactory {
  constructor(
    private configService: ConfigService,
    private tenantService: TenantService,
    @Inject(REQUEST) private readonly request: Request,
  ) {}
  async createMongooseOptions(): Promise<MongooseModuleOptions> {
    const host = this.request.hostname.split('.');
    const tenant = await this.tenantService.find({ slug: host[0] });
    if (tenant) {
      return {
        uri: this.configService.getOrThrow('MONGODB_URI'),
        dbName: host[0],
        lazyConnection: true,
        connectionFactory(connection: any, name: any) {
          connection.plugin(require('mongoose-autopopulate'));
          return connection;
        },
      };
    } else {
      throw new NotFoundException('Tenant not found!');
    }
  }
}

Auth Module

import { Global, Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { User, UserSchema } from './entities/user.entity';
import { ConnectionName } from 'src/utils/connectionn.enum';
import { UserController } from './controllers/user.controller';
import { UserService } from './services/user.service';
import { AuthController } from './controllers/auth.controller';
import { JwtModule } from '@nestjs/jwt';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { Role, RoleSchema } from './entities/role.entity';
import { RoleService } from './services/role.service';
import { RoleController } from './controllers/role.controller';

@Global()
@Module({
  imports: [
    MongooseModule.forFeature(
      [
        { name: Role.name, schema: RoleSchema },
        { name: User.name, schema: UserSchema },
      ],
      ConnectionName.CORE,
    ),
    JwtModule.registerAsync({
      imports: [ConfigModule],
      useFactory: async (configService: ConfigService) => ({
        global: true,
        secret: configService.getOrThrow('JWT_SECRET'),
        signOptions: {
          expiresIn: configService.getOrThrow('JWT_EXPIRES_IN'),
        },
      }),
      inject: [ConfigService],
    }),
  ],
  controllers: [AuthController, RoleController, UserController],
  providers: [RoleService, UserService],
  exports: [RoleService, UserService],
})
export class AuthModule {}

User Entity

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document, Schema as MongooseSchema } from 'mongoose';
import { Role } from './role.entity';

@Schema({
  versionKey: false,
  timestamps: {
    createdAt: 'created_at',
    updatedAt: 'updated_at',
  },
})
export class User extends Document {
  @Prop()
  username: string;

  @Prop()
  password: string;

  @Prop()
  email: string;

  @Prop()
  name: string;

  @Prop({
    type: [MongooseSchema.Types.ObjectId],
    ref: Role.name,
    autopopulate: true,
  })
  roles: Role[];
}

export const UserSchema = SchemaFactory.createForClass(User);

Minimum reproduction code

https://github.com/virames-tr/backend

Steps to reproduce

  1. npm i
  2. npm start:dev
  3. http://localhost:3000/api/v1/users
    ...see error

Expected behavior

I'm trying to set up a multi-tenant system. A different database will be used for each tenant. I shared my sample codes above. When I do it with useClass, the popuplate operation gives an error. If I connect with useFactory, this error does not occur. What could I be doing wrong or could this be a bug?

Package version

10.0.2

mongoose version

8.0.0

NestJS version

10.0.0

Node.js version

18.18.2

In which operating systems have you tested?

  • macOS
  • Windows
  • Linux

Other

No response

Please, use our Discord channel (support) for such questions. We are using GitHub to track bugs, feature requests, and potential improvements.