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,
})
Для ясности и для других разработчиков, чтобы они пришли в этот пост:
Если вы не задаете имя для подключения, будет установлено его имя по умолчанию. Обратите внимание, что у вас не должно быть нескольких подключений без имени или с одним и тем же именем, иначе они просто переопределятся.
Одно из ваших подключений должно иметь одно из следующего:
"name":"default"
- Без имени.
Я бы рекомендовал декларировать все ваши связи в 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# несколько баз данных