Базы данных
Nest является "агностиком" по отношению к базам данных, позволяя легко интегрироваться с любой базой данных SQL или NoSQL. У вас есть несколько вариантов, доступных вам, в зависимости от ваших предпочтений. На самом общем уровне подключение Nest к базе данных-это просто вопрос загрузки соответствующего Node.js драйвера для базы данных, так же, как и для Express или Fastify.
Вы также можете напрямую использовать любую библиотеку Node.js интеграции базы данных или ORM, например Sequelize (перейдите в раздел Sequelize integration), Knex.js (туториал) и TypeORM, чтобы работать на более высоком уровне абстракции.
Для удобства, Nest обеспечивает тесную интеграцию с TypeORM и Sequelize из коробки с помощью @nestjs/typeorm и @nestjs/sequelize пакетов соответственно, о которых мы расскажем в настоящей главе, и Mongoose с @nestjs/mongoose, который рассматривается в данной главе. Эти интеграции обеспечивают дополнительные функции, характерные для 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 для выбранной базы данных.
$ npm install --save @nestjs/typeorm typeorm mysqlПосле завершения процесса установки мы можем импортировать TypeOrmModule в корневой AppModule.
Метод forRoot() поддерживает все свойства конфигурации, предоставляемые функцией createConnection() из пакета TypeORM. Кроме того, существует несколько дополнительных свойств конфигурации, описанных ниже.
Свойство
Описание
retryAttempts
Количество попыток подключения к базе данных (по умолчанию: 10)
retryDelay
Задержка между повторными попытками подключения (мс) (по умолчанию: 3000)
autoLoadEntities
Если значение true, то сущности будут загружены автоматически (по умолчанию: false)
keepConnectionAlive
Если true, то соединение не будет закрыто при завершении работы приложения (по умолчанию: false)
Подробнее о параметрах подключения можно узнать здесь.
В качестве альтернативы, вместо передачи объекта конфигурации в forRoot(), мы можем создать файл ormconfig.json в корневом каталоге проекта.
{
"type": "mysql",
"host": "localhost",
"port": 3306,
"username": "root",
"password": "root",
"database": "test",
"entities": ["dist/**/*.entity{.ts,.js}"],
"synchronize": true
}Затем мы можем вызвать функцию forRoot() без каких-либо опций:
Статические глобальные пути (например, dist/**/*.entity{ .ts,.js} не будут работать должным образом с webpack.
Обратите внимание, что файл ormconfig.json загружается библиотекой typeorm. Таким образом, ни одно из дополнительных свойств, описанных выше (которые поддерживаются внутренне с помощью метода forRoot() - например, autoLoadEntities и retryDelay), не будет применено.
Как только это будет сделано, объекты Typeorm Connection и EntityManager будут доступны для внедрения по всему проекту (без необходимости импортировать какие-либо модули), например:
app.module.ts
Паттерн репозиторий
TypeORM поддерживает шаблон проектирования репозиторий, поэтому каждая сущность имеет свой собственный репозиторий. Эти репозитории можно получить из подключения к базе данных.
Чтобы продолжить пример, нам нужна хотя бы одна сущность. Давайте определим сущность User.
Узнайте больше о сущностях в документации TypeORM.
Файл сущности User находится в каталоге users. Этот каталог содержит все файлы, связанные с UsersModule. Вы можете решить, где хранить файлы вашей модели, однако мы рекомендуем создавать их рядом с их доменом, в соответствующем каталоге модулей.
Чтобы начать использовать сущность User, нам нужно сообщить об этом TypeORM, вставив ее в массив entities в параметрах метода forRoot() (если только вы не используете статический глобальный путь):
Далее, Давайте посмотрим на UsersModule:
Этот модуль использует метод forFeature() для определения того, какие репозитории зарегистрированы в текущей области видимости. Имея это на месте, мы можем ввести UsersRepository в UsersService с помощью декоратора @InjectRepository():
users.service.ts
Не забудьте импортировать UsersModule в корневой AppModule.
Если вы хотите использовать репозиторий вне модуля, который импортирует 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().
Чтобы узнать больше об отношениях в TypeORM, посетите документацию TypeORM.
Автозагрузка сущностей
Ручное добавление сущностей в массив entities параметров подключения может быть утомительным. Кроме того, ссылка на объекты из корневого модуля ломает границы домена приложения и приводит к утечке сведений о реализации в другие части приложения. Для решения этой проблемы можно использовать глобальные статические пути (например, dist/**/*.entity{ .ts,.js}).
Обратите внимание, однако, что глобальные пути не поддерживаются webpack, поэтому, если вы создаете свое приложение в рамках монорепозитория, вы не сможете их использовать. Для решения этой проблемы предлагается альтернативное решение. Для автоматической загрузки сущностей задайте свойству autoLoadEntities объекта конфигурации (переданного в метод forRoot()) значение true, как показано ниже:
Если этот параметр указан, то каждая сущность, зарегистрированная с помощью метода forFeature(), будет автоматически добавлена в массив entities объекта конфигурации.
Обратите внимание, что сущности, которые не зарегистрированы с помощью метода forFeature(), но на которые ссылаются только из сущности (через связь), не будут включены с помощью параметра autoLoadEntities.
Транзакции
Транзакция базы данных символизирует единицу работы, выполняемой в рамках системы управления базами данных по отношению к базе данных и обрабатываемой согласованным и надежным образом независимо от других транзакций. Транзакция обычно представляет собой любое изменение в базе данных (Подробнее).
Существует множество различных стратегий для обработки транзакций TypeORM. Мы рекомендуем использовать класс QueryRunner, поскольку он дает полный контроль над транзакцией.
Во-первых, нам нужно ввести объект Connection в класс обычным способом:
Класс Connection импортируется из пакета typeorm.
Теперь мы можем использовать этот объект для создания транзакции.
Обратите внимание,что connection используется только для создания QueryRunner. Однако для тестирования этого класса потребуется издеваться над всем объектом Connection (который предоставляет несколько методов). Таким образом, мы рекомендуем использовать вспомогательный фабричный класс (например, QueryRunnerFactory) и определить интерфейс с ограниченным набором методов, необходимых для поддержания транзакций. Эта техника делает мока-тестирование над этими методами довольно простой.
Кроме того, можно использовать подход в стиле колбэка с использованием метода transaction объекта Connection (подробнее).
Использование декораторов для управления транзакцией (@Transaction() и @TransactionManager()) не рекомендуется.
Подписчики
С помощью подписчиков TypeORM можно прослушивать определенные события сущностей.
Подписчики событий не могут быть ограничены областью запроса!
Теперь добавьте класс UserSubscriber в массив providers:
Узнать больше о подписчиках сущностей здесь.
Миграции
Миграции обеспечивают возможность постепенного обновления схемы базы данных, чтобы поддерживать ее синхронизацию с моделью данных приложения при сохранении существующих данных в базе данных. Чтобы создать, запустить и откатить миграции, TypeORM предоставляет специальный интерфейс командной строки.
Классы миграции отделены от исходного кода приложения Nest. Их жизненный цикл поддерживается CLI TypeORM. Таким образом, вы не можете использовать инъекцию зависимостей и другие специфические функции Nest с помощью миграции. Чтобы узнать больше о миграции, следуйте инструкциям в документации TypeORM.
Несколько баз данных
Некоторые проекты требуют нескольких подключений к базе данных. Это также может быть достигнуто с помощью этого модуля. Чтобы работать с несколькими соединениями, сначала создайте их. В этом случае именование соединений становится обязательным.
Предположим, что у вас есть сущность Album, хранящаяся в ее собственной базе данных.
Если вы не зададите name для соединения, то его имя будет установлено по умолчанию. Обратите внимание, что вы не должны иметь несколько соединений без имени или с одним и тем же именем, иначе они будут переопределены.
На этом этапе у вас есть User и Album сущности, зарегистрированные с их собственным подключением. С помощью этой настройки вы должны сообщить методуTypeOrmModule.forFeature() и декоратору @InjectRepository() какое соединение следует использовать. Если вы не передадите имя соединения, то будет использоваться соединение по умолчанию.
Вы также можете ввести Connection или EntityManager для данного соединения:
Тестирование
Когда дело доходит до модульного(Unit) тестирования приложения, мы обычно хотим избежать подключения к базе данных, сохраняя наши наборы тестов независимыми и процесс их выполнения как можно более быстрым. Но наши классы могут зависеть от репозиториев, которые извлекаются из экземпляра соединения. Как же нам с этим справиться? Решение заключается в создании макетных репозиториев. Для достижения этой цели мы создаем собственные провайдеры. Каждый зарегистрированный репозиторий автоматически представляется токеном <EntityName>Repository, где EntityName - это имя вашего класса сущностей.
Пакет @nestjs/typeorm предоставляет функцию getRepositoryToken(), которая возвращает подготовленный токен на основе заданной сущности.
Теперь заменитель mockRepository будет использоваться в качестве UserRepository. Всякий раз, когда какой-либо класс запрашивает UserRepository с помощью декоратора @InjectRepository(), Nest будет использовать зарегистрированный объект mockRepository.
Кастомные репозитории
TypeORM предоставляет функцию, называемую кастомные репозиториями. Кастомные репозитории позволяют расширить базовый класс репозитория и обогатить его несколькими специальными методами. Чтобы узнать больше об этой функции, посетите эту страницу.
Чтобы создать свой собственный репозиторий, используйте декоратор @EntityRepository() и расширьте класс репозитория.
Both @EntityRepository() and Repository are imported from the typeorm package.
После создания класса следующим шагом является делегирование ответственности за создание экземпляра Nest. Для этого мы должны передать класс AuthorRepository в метод TypeOrm.forFeature().
После этого просто инъектируйте репозиторий, используя следующую конструкцию:
Асинхронная конфигурация
Возможно, вы захотите передать параметры модуля репозитория асинхронно, а не статически. В этом случае используйте метод forRootAsync(), который предоставляет несколько способов работы с асинхронной конфигурацией.
Один из подходов заключается в использовании функции фабрики:
Наша фабрика ведет себя так же, как и любой другой асинхронный провайдер (например, он может быть async и может вводить зависимости через inject).
Альтернативно, мы может использовать синтаксис useClass:
Приведенная выше конструкция создаст экземпляр TypeOrmConfigService внутри TypeOrmModule и использует его для предоставления объекта options путем вызова функции createTypeOrmOptions(). Обратите внимание, что это означает, что TypeOrmConfigService должен реализовать интерфейс TypeOrmOptionsFactory, как показано ниже:
Чтобы предотвратить создание TypeOrmConfigService внутри TypeOrmModule и использовать провайдер, импортированный из другого модуля, можно использовать синтаксис useExisting.
Эта конструкция работает так же, как и useClass с одним критическим отличием - TypeOrmModule будет искать импортированные модули для повторного использования существующего ConfigService вместо создания нового экземпляра.
Пример
Рабочий пример доступен здесь.
Sequelize Integration
Потом как-нибудь:)
Last updated