# Базы данных

**Nest** является "агностиком" по отношению к базам данных, позволяя легко интегрироваться с любой базой данных **SQL** или **NoSQL**. У вас есть несколько вариантов, доступных вам, в зависимости от ваших предпочтений. На самом общем уровне подключение Nest к базе данных-это просто вопрос загрузки соответствующего Node.js драйвера для базы данных, так же, как и для **Express** или **Fastify**.

Вы также можете напрямую использовать любую библиотеку Node.js интеграции базы данных или **ORM**, например [**Sequelize**](https://sequelize.org/) (перейдите в раздел [**Sequelize integration**](https://docs.nestjs.com/techniques/database#sequelize-integration)), [**Knex.js**](http://knexjs.org/) ([**туториал**](https://dev.to/nestjs/build-a-nestjs-module-for-knex-js-or-other-resource-based-libraries-in-5-minutes-12an)) и [**TypeORM**](https://github.com/typeorm/typeorm), чтобы работать на более высоком уровне абстракции.

Для удобства, **Nest** обеспечивает тесную интеграцию с TypeORM и Sequelize из коробки с помощью `@nestjs/typeorm` и `@nestjs/sequelize` пакетов соответственно, о которых мы расскажем в настоящей главе, и **Mongoose** с `@nestjs/mongoose`, который рассматривается в [**данной главе**](https://docs.nestjs.com/techniques/mongodb). Эти интеграции обеспечивают дополнительные функции, характерные для **NestJS**, такие как внедрение модели/репозитория, тестируемость и асинхронная конфигурация, чтобы сделать доступ к выбранной базе данных еще проще.

### Интеграция TypeORM

Для интеграции с базами данных **SQL** и **NoSQL** Nest предоставляет пакет `@nestjs/typeorm`. Nest используется typeORM, потому что это наиболее зрелый объектно-реляционный mapper (ORM), доступный для **Typescript** . Поскольку он написан на Typescript, он хорошо интегрируется с Nest framework.

Чтобы начать его использовать, мы сначала устанавливаем необходимые зависимости. В этой главе мы продемонстрируем использование популярных реляционных СУБД MySQL, но TypeORM обеспечивает поддержку многих реляционных баз данных, таких как PostgreSQL, Oracle, Microsoft SQL Server, SQLite и даже баз данных NoSQL, таких как MongoDB. Процедура, которую мы рассмотрим в этой главе, будет одинаковой для любой базы данных, поддерживаемой TypeORM. Вам просто нужно будет установить соответствующие клиентские библиотеки API для выбранной базы данных.

```bash
$ npm install --save @nestjs/typeorm typeorm mysql
```

После завершения процесса установки мы можем импортировать `TypeOrmModule` в корневой `AppModule`.

Метод `forRoot()` поддерживает все свойства конфигурации, предоставляемые функцией `createConnection()` из пакета [**TypeORM**](https://typeorm.io/#/connection-options). Кроме того, существует несколько дополнительных свойств конфигурации, описанных ниже.

| Свойство            | Описание                                                                                         |
| ------------------- | ------------------------------------------------------------------------------------------------ |
| retryAttempts       | Количество попыток подключения к базе данных (по умолчанию: 10)                                  |
| retryDelay          | Задержка между повторными попытками подключения (мс) (по умолчанию: 3000)                        |
| autoLoadEntities    | Если значение true, то сущности будут загружены автоматически (по умолчанию: false)              |
| keepConnectionAlive | Если true, то соединение не будет закрыто при завершении работы приложения (по умолчанию: false) |

{% hint style="info" %}
Подробнее о параметрах подключения можно узнать [**здесь**](https://typeorm.io/#/connection-options).
{% endhint %}

В качестве альтернативы, вместо передачи объекта конфигурации в `forRoot()`, мы можем создать файл `ormconfig.json` в корневом каталоге проекта.

```typescript
{
  "type": "mysql",
  "host": "localhost",
  "port": 3306,
  "username": "root",
  "password": "root",
  "database": "test",
  "entities": ["dist/**/*.entity{.ts,.js}"],
  "synchronize": true
}
```

Затем мы можем вызвать функцию `forRoot()` без каких-либо опций:

{% hint style="info" %}
Статические глобальные пути (например, `dist/**/*.entity{ .ts,.js}` не будут работать должным образом с webpack.
{% endhint %}

{% hint style="info" %}
Обратите внимание, что файл `ormconfig.json` загружается библиотекой `typeorm`. Таким образом, ни одно из дополнительных свойств, описанных выше (которые поддерживаются внутренне с помощью метода `forRoot()` - например, `autoLoadEntities` и `retryDelay`), не будет применено.
{% endhint %}

Как только это будет сделано, объекты **Typeorm** `Connection` и `EntityManager` будут доступны для внедрения по всему проекту (без необходимости импортировать какие-либо модули), например:

*app.module.ts*

```typescript
import { Connection } from 'typeorm';

@Module({
  imports: [TypeOrmModule.forRoot(), UsersModule],
})
export class AppModule {
  constructor(private connection: Connection) {}
}

```

### Паттерн репозиторий

TypeORM поддерживает шаблон проектирования репозиторий, поэтому каждая сущность имеет свой собственный репозиторий. Эти репозитории можно получить из подключения к базе данных.

Чтобы продолжить пример, нам нужна хотя бы одна сущность. Давайте определим сущность `User`.

{% hint style="info" %}
Узнайте больше о сущностях в [**документации TypeORM**](https://typeorm.io/#/entities).
{% endhint %}

Файл сущности `User` находится в каталоге `users`. Этот каталог содержит все файлы, связанные с `UsersModule`. Вы можете решить, где хранить файлы вашей модели, однако мы рекомендуем создавать их рядом с их **доменом**, в соответствующем каталоге модулей.

Чтобы начать использовать сущность **User**, нам нужно сообщить об этом **TypeORM**, вставив ее в массив `entities` в параметрах метода `forRoot()` (если только вы не используете статический глобальный путь):

Далее, Давайте посмотрим на `UsersModule`:

Этот модуль использует метод `forFeature()` для определения того, какие репозитории зарегистрированы в текущей области видимости. Имея это на месте, мы можем ввести `UsersRepository` в `UsersService` с помощью декоратора `@InjectRepository()`:

*users.service.ts*

```typescript
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private usersRepository: Repository<User>,
  ) {}

  findAll(): Promise<User[]> {
    return this.usersRepository.find();
  }

  findOne(id: string): Promise<User> {
    return this.usersRepository.findOne(id);
  }

  async remove(id: string): Promise<void> {
    await this.usersRepository.delete(id);
  }
}
```

{% hint style="info" %}
Не забудьте импортировать UsersModule в корневой AppModule.
{% endhint %}

Если вы хотите использовать репозиторий вне модуля, который импортирует `TypeOrmModule.forFeature`, вам нужно будет реэкспортировать провайдеров, сгенерированных им. Вы можете сделать это, экспортировав весь модуль, например:

Если мы импортируем `UsersModule` в `UserHttpModule`, мы можем использовать `@InjectRepository(User)` в провайдерах последнего модуля.

### Отношения

Отношения - это ассоциации, устанавливаемые между двумя или более таблицами. Отношения основаны на общих полях из каждой таблицы, часто включающих первичный(primary) и внешний(foreign) ключи.

Существует три типа отношений:

| Тип отношения             | Описание                                                                                                                                                                                                                                        |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| One-to-one                | Каждая строка в основной таблице имеет одну и только одну связанную строку во внешней таблице. Используйте декоратор `@OneToOne()`, чтобы определить этот тип отношения.                                                                        |
| One-to-many / Many-to-one | Каждая строка в основной таблице имеет одну или несколько связанных строк во внешней таблице. Используйте декораторы `@OneToMany()` и `@ManyToOne()`, чтобы определить этот тип отношения.                                                      |
| Many-to-many              | Каждая строка в основной таблице имеет много связанных строк во внешней таблице, и каждая запись во внешней таблице имеет много связанных строк в основной таблице. Используйте декоратор `@ManyToMany()`, чтобы определить этот тип отношения. |

Чтобы определить отношения в сущностях, используйте соответствующие декораторы. Например, чтобы определить, что у каждого пользователя может быть несколько фотографий, используйте декоратор `@OneToMany()`.

{% hint style="info" %}
Чтобы узнать больше об отношениях в TypeORM, посетите [**документацию TypeORM**](https://typeorm.io/#/relations).
{% endhint %}

### Автозагрузка сущностей

Ручное добавление сущностей в массив `entities` параметров подключения может быть утомительным. Кроме того, ссылка на объекты из корневого модуля ломает границы домена приложения и приводит к утечке сведений о реализации в другие части приложения. Для решения этой проблемы можно использовать глобальные статические пути (например, `dist/**/*.entity{ .ts,.js}`).

Обратите внимание, однако, что глобальные пути не поддерживаются **webpack**, поэтому, если вы создаете свое приложение в рамках монорепозитория, вы не сможете их использовать. Для решения этой проблемы предлагается альтернативное решение. Для автоматической загрузки сущностей задайте свойству `autoLoadEntities` объекта конфигурации (переданного в метод `forRoot()`) значение `true`, как показано ниже:

Если этот параметр указан, то каждая сущность, зарегистрированная с помощью метода `forFeature()`, будет автоматически добавлена в массив `entities` объекта конфигурации.

{% hint style="info" %}
Обратите внимание, что сущности, которые не зарегистрированы с помощью метода `forFeature()`, но на которые ссылаются только из сущности (через связь), не будут включены с помощью параметра `autoLoadEntities.`
{% endhint %}

### Транзакции

Транзакция базы данных символизирует единицу работы, выполняемой в рамках системы управления базами данных по отношению к базе данных и обрабатываемой согласованным и надежным образом независимо от других транзакций. Транзакция обычно представляет собой любое изменение в базе данных ([**Подробнее**](https://en.wikipedia.org/wiki/Database_transaction)).

Существует множество различных стратегий для обработки [**транзакций TypeORM**](https://typeorm.io/#/transactions). Мы рекомендуем использовать класс `QueryRunner`, поскольку он дает полный контроль над транзакцией.

Во-первых, нам нужно ввести объект `Connection` в класс обычным способом:

```typescript
@Injectable()
export class UsersService {
  constructor(private connection: Connection) {}
}
```

{% hint style="info" %}
Класс `Connection` импортируется из пакета `typeorm`.
{% endhint %}

Теперь мы можем использовать этот объект для создания транзакции.

```typescript
async createMany(users: User[]) {
  const queryRunner = this.connection.createQueryRunner();

  await queryRunner.connect();
  await queryRunner.startTransaction();
  try {
    await queryRunner.manager.save(users[0]);
    await queryRunner.manager.save(users[1]);

    await queryRunner.commitTransaction();
  } catch (err) {
    // since we have errors lets rollback the changes we made
    await queryRunner.rollbackTransaction();
  } finally {
    // you need to release a queryRunner which was manually instantiated
    await queryRunner.release();
  }
}
```

{% hint style="info" %}
Обратите внимание,что `connection` используется только для создания `QueryRunner`. Однако для тестирования этого класса потребуется издеваться над всем объектом `Connection` (который предоставляет несколько методов). Таким образом, мы рекомендуем использовать вспомогательный фабричный класс (например, `QueryRunnerFactory`) и определить интерфейс с ограниченным набором методов, необходимых для поддержания транзакций. Эта техника делает мока-тестирование над этими методами довольно простой.
{% endhint %}

Кроме того, можно использовать подход в стиле колбэка с использованием метода `transaction` объекта `Connection` ([**подробнее**](https://typeorm.io/#/transactions/creating-and-using-transactions)).

```typescript
async createMany(users: User[]) {
  await this.connection.transaction(async manager => {
    await manager.save(users[0]);
    await manager.save(users[1]);
  });
}
```

Использование декораторов для управления транзакцией (`@Transaction()` и `@TransactionManager()`) не рекомендуется.

### Подписчики

С помощью [**подписчиков**](https://typeorm.io/#/listeners-and-subscribers/what-is-a-subscriber) TypeORM можно прослушивать определенные события сущностей.

```typescript
import {
  Connection,
  EntitySubscriberInterface,
  EventSubscriber,
  InsertEvent,
} from 'typeorm';
import { User } from './user.entity';

@EventSubscriber()
export class UserSubscriber implements EntitySubscriberInterface<User> {
  constructor(connection: Connection) {
    connection.subscribers.push(this);
  }

  listenTo() {
    return User;
  }

  beforeInsert(event: InsertEvent<User>) {
    console.log(`BEFORE USER INSERTED: `, event.entity);
  }
}
```

{% hint style="info" %}
Подписчики событий не могут быть ограничены областью запроса!
{% endhint %}

Теперь добавьте класс `UserSubscriber` в массив `providers`:

```typescript
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './user.entity';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { UserSubscriber } from './user.subscriber';

@Module({
  imports: [TypeOrmModule.forFeature([User])],
  providers: [UsersService, UserSubscriber],
  controllers: [UsersController],
})
export class UsersModule {}
```

{% hint style="info" %}
Узнать больше о подписчиках сущностей [**здесь**](https://typeorm.io/#/listeners-and-subscribers/what-is-a-subscriber).
{% endhint %}

### Миграции

[**Миграции**](https://typeorm.io/#/migrations) обеспечивают возможность постепенного обновления схемы базы данных, чтобы поддерживать ее синхронизацию с моделью данных приложения при сохранении существующих данных в базе данных. Чтобы создать, запустить и откатить миграции, TypeORM предоставляет специальный [**интерфейс командной строки**](https://typeorm.io/#/migrations/creating-a-new-migration).

Классы миграции отделены от исходного кода приложения **Nest**. Их жизненный цикл поддерживается **CLI TypeORM**. Таким образом, вы **не можете** использовать инъекцию зависимостей и другие специфические функции **Nest** с помощью миграции. Чтобы узнать больше о миграции, следуйте инструкциям в [**документации**](https://typeorm.io/#/migrations/creating-a-new-migration) **TypeORM**.

### Несколько баз данных

Некоторые проекты требуют нескольких подключений к базе данных. Это также может быть достигнуто с помощью этого модуля. Чтобы работать с несколькими соединениями, сначала создайте их. В этом случае именование соединений становится обязательным.

Предположим, что у вас есть сущность `Album`, хранящаяся в ее собственной базе данных.

```typescript
const defaultOptions = {
  type: 'postgres',
  port: 5432,
  username: 'user',
  password: 'password',
  database: 'db',
  synchronize: true,
};

@Module({
  imports: [
    TypeOrmModule.forRoot({
      ...defaultOptions,
      host: 'user_db_host',
      entities: [User],
    }),
    TypeOrmModule.forRoot({
      ...defaultOptions,
      name: 'albumsConnection',
      host: 'album_db_host',
      entities: [Album],
    }),
  ],
})
export class AppModule {}
```

{% hint style="info" %}
Если вы не зададите `name` для соединения, то его имя будет установлено по умолчанию. Обратите внимание, что вы **не должны** иметь несколько соединений без имени или с одним и тем же именем, иначе они будут переопределены.
{% endhint %}

На этом этапе у вас есть `User` и `Album` сущности, зарегистрированные с их собственным подключением. С помощью этой настройки вы должны сообщить методу`TypeOrmModule.forFeature()` и декоратору `@InjectRepository()` какое соединение следует использовать. Если вы не передадите имя соединения, то будет использоваться соединение по умолчанию.

```typescript
@Module({
  imports: [
    TypeOrmModule.forFeature([User]),
    TypeOrmModule.forFeature([Album], 'albumsConnection'),
  ],
})
export class AppModule {}
```

Вы также можете ввести `Connection` или `EntityManager` для данного соединения:

```typescript
@Injectable()
export class AlbumsService {
  constructor(
    @InjectConnection('albumsConnection')
    private connection: Connection,
    @InjectEntityManager('albumsConnection')
    private entityManager: EntityManager,
  ) {}
}

```

### Тестирование

Когда дело доходит до модульного(**Unit**) тестирования приложения, мы обычно хотим избежать подключения к базе данных, сохраняя наши наборы тестов независимыми и процесс их выполнения как можно более быстрым. Но наши классы могут зависеть от **репозиториев**, которые извлекаются из экземпляра соединения. Как же нам с этим справиться? Решение заключается в создании **макетных репозиториев**. Для достижения этой цели мы создаем собственные провайдеры. Каждый зарегистрированный репозиторий автоматически представляется токеном `<EntityName>Repository`, где `EntityName` - это имя вашего класса сущностей.

Пакет `@nestjs/typeorm` предоставляет функцию `getRepositoryToken()`, которая возвращает подготовленный токен на основе заданной сущности.

```typescript
@Module({
  providers: [
    UserService,
    {
      provide: getRepositoryToken(User),
      useValue: mockRepository,
    },
  ],
})
export class UsersModule {}
```

Теперь заменитель `mockRepository` будет использоваться в качестве `UserRepository`. Всякий раз, когда какой-либо класс запрашивает `UserRepository` с помощью декоратора `@InjectRepository()`, **Nest** будет использовать зарегистрированный объект `mockRepository`.

### Кастомные репозитории

**TypeORM** предоставляет функцию, называемую **кастомные репозиториями**. Кастомные репозитории позволяют расширить базовый класс репозитория и обогатить его несколькими специальными методами. Чтобы узнать больше об этой функции, посетите [**эту страницу**](https://typeorm.io/#/custom-repository).

Чтобы создать свой собственный репозиторий, используйте декоратор `@EntityRepository()` и расширьте класс репозитория.

```typescript
@EntityRepository(Author)
export class AuthorRepository extends Repository<Author> {}
```

{% hint style="info" %}
Both `@EntityRepository()` and `Repository` are imported from the `typeorm` package.
{% endhint %}

После создания класса следующим шагом является делегирование ответственности за создание экземпляра **Nest**. Для этого мы должны передать класс `AuthorRepository` в метод `TypeOrm.forFeature()`.

```typescript
@Module({
  imports: [TypeOrmModule.forFeature([AuthorRepository])],
  controller: [AuthorController],
  providers: [AuthorService],
})
export class AuthorModule {}
```

После этого просто инъектируйте репозиторий, используя следующую конструкцию:

```typescript
@Injectable()
export class AuthorService {
  constructor(private authorRepository: AuthorRepository) {}
}

```

### Асинхронная конфигурация

Возможно, вы захотите передать параметры модуля репозитория асинхронно, а не статически. В этом случае используйте метод `forRootAsync()`, который предоставляет несколько способов работы с асинхронной конфигурацией.

Один из подходов заключается в использовании функции фабрики:

```typescript
TypeOrmModule.forRootAsync({
  useFactory: () => ({
    type: 'mysql',
    host: 'localhost',
    port: 3306,
    username: 'root',
    password: 'root',
    database: 'test',
    entities: [__dirname + '/**/*.entity{.ts,.js}'],
    synchronize: true,
  }),
});
```

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

```typescript
TypeOrmModule.forRootAsync({
  imports: [ConfigModule],
  useFactory: (configService: ConfigService) => ({
    type: 'mysql',
    host: configService.get<string>('HOST'),
    port: configService.get<string>('PORT'),
    username: configService.get<string>('USERNAME'),
    password: configService.get<string>('PASSWORD'),
    database: configService.get<string>('DATABASE'),
    entities: [__dirname + '/**/*.entity{.ts,.js}'],
    synchronize: true,
  }),
  inject: [ConfigService],
});
```

Альтернативно, мы может использовать синтаксис `useClass`:

```typescript
TypeOrmModule.forRootAsync({
  useClass: TypeOrmConfigService,
});
```

Приведенная выше конструкция создаст экземпляр `TypeOrmConfigService` внутри `TypeOrmModule` и использует его для предоставления объекта `options` путем вызова функции `createTypeOrmOptions()`. Обратите внимание, что это означает, что `TypeOrmConfigService` должен реализовать интерфейс `TypeOrmOptionsFactory`, как показано ниже:

```typescript
@Injectable()
class TypeOrmConfigService implements TypeOrmOptionsFactory {
  createTypeOrmOptions(): TypeOrmModuleOptions {
    return {
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'root',
      database: 'test',
      entities: [__dirname + '/**/*.entity{.ts,.js}'],
      synchronize: true,
    };
  }
}
```

Чтобы предотвратить создание `TypeOrmConfigService` внутри `TypeOrmModule` и использовать провайдер, импортированный из другого модуля, можно использовать синтаксис `useExisting`.

```typescript
TypeOrmModule.forRootAsync({
  imports: [ConfigModule],
  useExisting: ConfigService,
});
```

Эта конструкция работает так же, как и `useClass` с одним критическим отличием - `TypeOrmModule` будет искать импортированные модули для повторного использования существующего `ConfigService` вместо создания нового экземпляра.

### Пример

Рабочий пример доступен [**здесь**](https://github.com/nestjs/nest/tree/master/sample/05-sql-typeorm).

### Sequelize Integration

Потом как-нибудь:)
