NestJS + TypeORM: использовать две или более баз данных?

Я пытаюсь решить эту проблему за 2 дня, возможно, я просто упускаю суть здесь.

Моей целью было написать приложение NestJS (с включенным TypeORM), которое обслуживает RestAPI для 2 или 3 моих маленьких проектов, вместо того, чтобы писать NestJS-приложение для каждого из них.

Пока все хорошо, приложение готово, хорошо работает с отдельными проектами (которые находятся в подпапках с их сущностями, контроллерами, сервисами, модулями), но я не могу заставить его работать со всеми из них.

Дело в конфигурации, я использую ormconfig.json:

[ {
    "name": "Project1",
    "type": "mysql",
    "host": "localhost",
    "port": 3306,
    "username": "<username>",
    "password": "<pwd>",
    "database": "<database>",
    "synchronize": false,
    "entities": ["project1/*.entity.ts"],
    "subscribers": ["project1/*.subscriber.ts"],
    "migrations": ["project1/migrations/*.ts"],
    "cli": { "migrationsDir": "project1/migrations" }
}, {
    "name": "project2",
    "type": "mysql",
    "host": "localhost",
    "port": 3306,
    "username": "<another-username>",
    "password": "<another-pwd>",
    "database": "<another-database>",
    "synchronize": false,
    "entities": ["project2/*.entity.ts"],
    "subscribers": ["project2/*.subscriber.ts"],
    "migrations": ["project2/migrations/*.ts"],
    "cli": { "migrationsDir": "project2/migrations"
    } ]

Сообщение об ошибке говорит:

[ExceptionHandler] Не удается найти соединение по умолчанию, поскольку оно не определено ни в одном файле конфигурации orm.

Конечно, "default" не удалось найти, потому что я предоставляю две конфигурации с уникальными именами, отличными от "default".

В ApplicationModule я мог бы указать имя соединения, например:

TypeOrmModule.forRoot( { name: "project1" } ),

но тогда это будет работать только для одного проекта.

Я мог бы смешать все в одном конфиге, но тогда у меня было бы все в одной базе данных, один и тот же пользователь для всех и, возможно, перепутать сущности...

Может кто-нибудь подсказать, как это решить? Возможно с getConnection(<name>) в каждом модуле, но как запустить ApplicationModule?

С уважением,
sagerobert

7 ответов

Решение

Я просто попытался настроить TypeORM с несколькими базами данных и ormconfig.json и это не сработало для меня вообще. Казалось, всегда использовать default соединение и когда не было найдено соединения по умолчанию (= без явного имени), выдается соответствующая ошибка.

Это сработало, хотя, когда я определил соединения в app.module.ts вместо (я удалил ormconfig.json):

imports: [
  ...,
  TypeOrmModule.forRoot({
    name: 'Project1',
    type: 'mysql',
    host: 'localhost',
    port: 3306,
    username: '<username>',
    password: '<pwd>',
    database: '<database>',
    synchronize: false,
    entities: ['project1/*.entity.ts'],
    subscribers: ['project1/*.subscriber.ts'],
    migrations: ['project1/migrations/*.ts'],
    cli: { migrationsDir: 'project1/migrations' },
  }),
  TypeOrmModule.forRoot({
    name: 'project2',
    type: 'mysql',
    host: 'localhost',
    port: 3306,
    username: '<another-username>',
    password: '<another-pwd>',
    database: '<another-database>',
    synchronize: false,
    entities: ['project2/*.entity.ts'],
    subscribers: ['project2/*.subscriber.ts'],
    migrations: ['project2/migrations/*.ts'],
    cli: { migrationsDir: 'project2/migrations' },
  })
]

Вам необходимо явно передать имя соединения на том же уровне внутри TypeOrmModule.forRoot({ name: 'db1Connection' }), если вы используете несколько соединений с базой данных.

TypeOrmModule.forRootAsync({
  name: DB1_CONNECTION,
  imports: [ConfigModule],
  useClass: TypeormDb1ConfigService,
}),

TypeOrmModule.forRootAsync({
  name: DB2_CONNECTION,
  imports: [ConfigModule],
  useClass: TypeormDb2ConfigService,
})

Для ясности и для других разработчиков, чтобы они пришли в этот пост:

Из документации NestJS:

Если вы не задаете имя для подключения, будет установлено его имя по умолчанию. Обратите внимание, что у вас не должно быть нескольких подключений без имени или с одним и тем же именем, иначе они просто переопределятся.

Одно из ваших подключений должно иметь одно из следующего:

  1. "name":"default"
  2. Без имени.

Я бы рекомендовал декларировать все ваши связи в ormconfig.json и не объявлять это в коде.

Пример импорта подключений из ormconfig.json:

@Module({
    imports: [TypeOrmModule.forFeature([Entity1, Entity2]), //This will use default connection
    TypeOrmModule.forRoot({name: 'con1'}), // This will register globaly con1
    TypeOrmModule.forRoot({name: 'con2'}), // This will register globaly con2
    controllers: [...],
    providers: [...],
    exports: [...]
})

в вашем модуле (не обязательно должен быть корневой модуль, только модули, которые вам понадобятся для подключения).

для тех, кто сталкивается с этой проблемой, это мое решение

AppModule

      @Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      load: [
        database,
        databaseAllo
      ]
    }),
    TypeOrmModule.forRootAsync({
      useFactory: (configs: ConfigService) => configs.get("db_config"),
      inject: [ConfigService],
    }),
    TypeOrmModule.forRootAsync({
      name:"db_allo", <= create connection to my second db
      useFactory: (configs: ConfigService) => configs.get("db_config_allo"),
      inject: [ConfigService],
    }),
    AuthModule,
    JwtAuthModule
  ],
  controllers: []
})
export class AppModule {}

мой проектный модуль (содержит таблицу из второй базы данных)

      
@Module({
  imports: [
    TypeOrmModule.forFeature([AlloMpcTable], "db_allo" <= call connection again),
  ],
  providers: [
    AlloRepository
  ],
  exports: [AlloRepository],
  controllers: [],
})
export class AlloModule {}

репозиторий моего проекта

      
@Injectable()
export class AlloRepository extends BaseRepository<AlloMpcTable> {
  constructor(
    @InjectRepository(AlloMpcTable, "db_allo") <= you need to call connection again
    private readonly allo: Repository<AlloMpcTable>,
  ) {
    super(allo)
  }

  public async Find(id: number): Promise<AlloMpcTable> {
    return await this.allo.findOne(id)
  }

}

Вот как мне удалось это исправить. С помощью одного файла конфигурации я могу запускать миграции в ускоренном режиме приложения или с помощью интерфейса командной строки TypeOrm.

SRC / config / ormconfig.ts

      import parseBoolean from '@eturino/ts-parse-boolean';
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
import * as dotenv from 'dotenv';
import { join } from 'path';

dotenv.config();

export = [
  {
    //name: 'default',
    type: 'mssql',
    host: process.env.DEFAULT_DB_HOST,
    username: process.env.DEFAULT_DB_USERNAME,
    password: process.env.DEFAULT_DB_PASSWORD,
    database: process.env.DEFAULT_DB_NAME,
    options: {
      instanceName: process.env.DEFAULT_DB_INSTANCE,
      enableArithAbort: false,
    },
    logging: parseBoolean(process.env.DEFAULT_DB_LOGGING),
    dropSchema: false,
    synchronize: false,
    migrationsRun: parseBoolean(process.env.DEFAULT_DB_RUN_MIGRATIONS),
    migrations: [join(__dirname, '..', 'model/migration/*.{ts,js}')],
    cli: {
      migrationsDir: 'src/model/migration',
    },
    entities: [
      join(__dirname, '..', 'model/entity/default/**/*.entity.{ts,js}'),
    ],
  } as TypeOrmModuleOptions,
  {
    name: 'other',
    type: 'mssql',
    host: process.env.OTHER_DB_HOST,
    username: process.env.OTHER_DB_USERNAME,
    password: process.env.OTHER_DB_PASSWORD,
    database: process.env.OTHER_DB_NAME,
    options: {
      instanceName: process.env.OTHER_DB_INSTANCE,
      enableArithAbort: false,
    },
    logging: parseBoolean(process.env.OTHER_DB_LOGGING),
    dropSchema: false,
    synchronize: false,
    migrationsRun: false,
    entities: [],
  } as TypeOrmModuleOptions,
];

src / app.module.ts

      import configuration from '@config/configuration';
import validationSchema from '@config/validation';
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { LoggerService } from '@shared/logger/logger.service';
import { UsersModule } from '@user/user.module';
import { AppController } from './app.controller';
import ormconfig = require('./config/ormconfig'); //path mapping doesn't work here

@Module({
  imports: [
    ConfigModule.forRoot({
      cache: true,
      isGlobal: true,
      validationSchema: validationSchema,
      load: [configuration],
    }),
    TypeOrmModule.forRoot(ormconfig[0]), //default
    TypeOrmModule.forRoot(ormconfig[1]), //other db
    LoggerService,
    UsersModule,
  ],
  controllers: [AppController],
})
export class AppModule {}

package.json

        "scripts": {
    ...
    "typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js --config ./src/config/ormconfig.ts",
    "typeorm:migration:generate": "npm run typeorm -- migration:generate -n",
    "typeorm:migration:run": "npm run typeorm -- migration:run"
  },

Структура проекта

      src/
├── app.controller.ts
├── app.module.ts
├── config
│   ├── configuration.ts
│   ├── ormconfig.ts
│   └── validation.ts
├── main.ts
├── model
│   ├── entity
│   ├── migration
│   └── repository
├── route
│   └── user
└── shared
    └── logger

Ответ @DamarOwen сработал для меня после того, как я наткнулся на другую ошибку: если вы хотите сохранить имя соединения в качестве переменной, не экспортируйте эту константу из . Сохраните переменную в другом файле, например .


Вот что я пробовал:

app.module.ts

      export const DB_CONNECTION_1 = 'conn1';

@Module({
  imports: [
    TypeOrmModule.forRootAsync({
      name: DB_CONNECTION_1,
      ...
    })
  ],
  ...
)
export class AppModule {}

база данных.модуль.тс

      @Module({
  imports: [
    TypeOrmModule.forFeature(
      [MyRepo],
      DB_CONNECTION_1,
    ),
  ],
  providers: [MyRepo],
})

мой.repo.ts

      @Injectable()
export class MyRepo {
  constructor(
    @InjectRepository(MyOrmEntity, DB_CONNECTION_1)
    private readonly repo: Repository<MyOrmEntity>,
  ) {}
}

Это вызвало ошибку No repository found for MyOrmEntity was found. Looks like this entity is not registered in current "default" connection.(Примечание. В моем приложении есть еще одно соединение с именем «по умолчанию»).

мне пришлось переехать export const DB_CONNECTION_1 = 'conn1';подальше от app.module.tsв свой собственный файл constants.ts. Тогда это сработало.

Просто быстрый PSA, возможно, это отсутствовало в то время, когда был задан вопрос, но теперь у NestJS есть полная документация по этой теме, включая все подводные камни, на которые обращаются другие ответы здесь: https://docs.nestjs.com/techniques/database# несколько баз данных

Другие вопросы по тегам