Usullar24 min read

Ma'lumotlar bazasi

Nest ma'lumotlar bazasiga bog'lanmagan (database agnostic), shuning uchun siz istalgan SQL yoki NoSQL bazani oson integratsiya qilishingiz mumkin. Tanlovlaringizga qarab bir nechta

Nest ma'lumotlar bazasiga bog'lanmagan (database agnostic), shuning uchun siz istalgan SQL yoki NoSQL bazani oson integratsiya qilishingiz mumkin. Tanlovlaringizga qarab bir nechta variant mavjud. Eng umumiy holatda, Nestni ma'lumotlar bazasiga ulash shunchaki mos Node.js driverini yuklash bilan cheklanadi, xuddi Express yoki Fastify bilan qilganingiz kabi.

Shuningdek, siz MikroORM (MikroORM recipega qarang), Sequelize (Sequelize integrationga qarang), Knex.js (Knex.js tutorialga qarang), TypeORM va Prisma (Prisma recipega qarang) kabi umumiy maqsadli Node.js ma'lumotlar bazasi integratsiya kutubxonasi yoki ORMdan ham to'g'ridan-to'g'ri foydalanishingiz mumkin, bu yuqoriroq abstraksiya darajasida ishlash imkonini beradi.

Qulaylik uchun Nest TypeORM va Sequelize bilan mos ravishda @nestjs/typeorm va @nestjs/sequelize paketlari orqali tayyor integratsiyani taqdim etadi; bu bobda shularni ko'rib chiqamiz. Shuningdek, Mongoose bilan @nestjs/mongoose integratsiyasi ham mavjud, u bu bobda yoritilgan. Bu integratsiyalar model/repository injection, testlanish imkoniyati va asinxron konfiguratsiya kabi NestJS ga xos qo'shimcha imkoniyatlarni taqdim etadi, bu esa tanlangan bazaga kirishni yanada osonlashtiradi.

TypeORM integratsiyasi

SQL va NoSQL bazalari bilan integratsiya qilish uchun Nest @nestjs/typeorm paketini taqdim etadi. TypeORM TypeScript uchun mavjud eng pishiq Object Relational Mapper (ORM). U TypeScriptda yozilganligi sababli, Nest freymvorki bilan yaxshi integratsiya qilinadi.

Undan foydalanishni boshlash uchun avval kerakli bog'liqliklarni o'rnatamiz. Bu bobda biz mashhur MySQL relatsion DBMS ni ishlatib ko'rsatamiz, ammo TypeORM ko'plab relatsion bazalarni (PostgreSQL, Oracle, Microsoft SQL Server, SQLite) va hatto MongoDB kabi NoSQL bazalarni qo'llab-quvvatlaydi. Bu bobda ko'rsatiladigan tartib TypeORM qo'llab-quvvatlaydigan istalgan baza uchun bir xil bo'ladi. Siz faqat tanlangan bazangizga mos client API kutubxonalarini o'rnatishingiz kerak bo'ladi.

Terminal
1$ npm install --save @nestjs/typeorm typeorm mysql2

O'rnatish jarayoni tugagach, TypeOrmModule ni root AppModule ga import qilamiz.

TypeScript
app.module
1import { Module } from '@nestjs/common';
2import { TypeOrmModule } from '@nestjs/typeorm';
3
4@Module({
5  imports: [
6    TypeOrmModule.forRoot({
7      type: 'mysql',
8      host: 'localhost',
9      port: 3306,
10      username: 'root',
11      password: 'root',
12      database: 'test',
13      entities: [],
14      synchronize: true,
15    }),
16  ],
17})
18export class AppModule {}
Warning

synchronize: true ni productionda ishlatish tavsiya etilmaydi - aks holda production ma'lumotlarini yo'qotishingiz mumkin.

forRoot() metodi TypeORM paketidagi DataSource konstruktori taqdim etadigan barcha konfiguratsiya xossalarini qo'llab-quvvatlaydi. Bundan tashqari, quyida tasvirlangan bir nechta qo'shimcha konfiguratsiya xossalari mavjud.

retryAttemptsMa'lumotlar bazasiga ulanishga urinishlar soni (default: 10)
retryDelayUlanishni qayta urinib ko'rishlar orasidagi kechikish (ms) (default: 3000)
autoLoadEntitiesAgar true bo'lsa, entitylar avtomatik yuklanadi (default: false)
Hint

Data source opsiyalari haqida batafsil bu yerda o'qing.

Shu ish bajarilgach, TypeORM ning DataSource va EntityManager obyektlari butun loyiha bo'ylab inject qilish uchun mavjud bo'ladi (hech qanday modul importini talab qilmasdan), masalan:

TypeScript
app.module
1import { DataSource } from 'typeorm';
2
3@Module({
4  imports: [TypeOrmModule.forRoot(), UsersModule],
5})
6export class AppModule {
7  constructor(private dataSource: DataSource) {}
8}

Repository pattern

TypeORM repository design pattern ni qo'llab-quvvatlaydi, shuning uchun har bir entity o'z repositorysiga ega. Bu repositorylarni ma'lumotlar bazasi data source dan olish mumkin.

Misolni davom ettirish uchun kamida bitta entity kerak. User entityni aniqlaymiz.

TypeScript
user.entity
1import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
2
3@Entity()
4export class User {
5  @PrimaryGeneratedColumn()
6  id: number;
7
8  @Column()
9  firstName: string;
10
11  @Column()
12  lastName: string;
13
14  @Column({ default: true })
15  isActive: boolean;
16}
Hint

Entitylar haqida batafsil ma'lumot uchun TypeORM hujjatlari ga qarang.

User entity fayli users direktoriyasida joylashadi. Bu direktoriyada UsersModule ga tegishli barcha fayllar bo'ladi. Model fayllarini qayerda saqlashni o'zingiz tanlashingiz mumkin, ammo ularni mos modul direktoriyasida, o'z domen obyektlari yonida saqlashni tavsiya qilamiz.

User entitydan foydalanishni boshlash uchun, uni moduldagi forRoot() metodi opsiyalaridagi entities massiviga qo'shib, TypeORM ga bildirishimiz kerak (agar statik glob path ishlatmasangiz):

TypeScript
app.module
1import { Module } from '@nestjs/common';
2import { TypeOrmModule } from '@nestjs/typeorm';
3import { User } from './users/user.entity';
4
5@Module({
6  imports: [
7    TypeOrmModule.forRoot({
8      type: 'mysql',
9      host: 'localhost',
10      port: 3306,
11      username: 'root',
12      password: 'root',
13      database: 'test',
14      entities: [User],
15      synchronize: true,
16    }),
17  ],
18})
19export class AppModule {}

Endi UsersModule ni ko'rib chiqamiz:

TypeScript
users.module
1import { Module } from '@nestjs/common';
2import { TypeOrmModule } from '@nestjs/typeorm';
3import { UsersService } from './users.service';
4import { UsersController } from './users.controller';
5import { User } from './user.entity';
6
7@Module({
8  imports: [TypeOrmModule.forFeature([User])],
9  providers: [UsersService],
10  controllers: [UsersController],
11})
12export class UsersModule {}

Bu modul forFeature() metodidan foydalanib joriy scope da qaysi repositorylar ro'yxatdan o'tkazilishini belgilaydi. Shundan so'ng @InjectRepository() dekoratori yordamida UsersService ga UsersRepository ni inject qilishimiz mumkin:

TypeScript
users.service
1import { Injectable } from '@nestjs/common';
2import { InjectRepository } from '@nestjs/typeorm';
3import { Repository } from 'typeorm';
4import { User } from './user.entity';
5
6@Injectable()
7export class UsersService {
8  constructor(
9    @InjectRepository(User)
10    private usersRepository: Repository<User>,
11  ) {}
12
13  findAll(): Promise<User[]> {
14    return this.usersRepository.find();
15  }
16
17  findOne(id: number): Promise<User | null> {
18    return this.usersRepository.findOneBy({ id });
19  }
20
21  async remove(id: number): Promise<void> {
22    await this.usersRepository.delete(id);
23  }
24}
Notice

UsersModule ni root AppModule ga import qilishni unutmang.

Agar TypeOrmModule.forFeature import qilgan moduldan tashqarida repositorydan foydalanmoqchi bo'lsangiz, u yaratgan providerlarni qayta eksport qilishingiz kerak. Buni butun modulni export qilish orqali qilasiz, quyidagicha:

TypeScript
users.module
1import { Module } from '@nestjs/common';
2import { TypeOrmModule } from '@nestjs/typeorm';
3import { User } from './user.entity';
4
5@Module({
6  imports: [TypeOrmModule.forFeature([User])],
7  exports: [TypeOrmModule]
8})
9export class UsersModule {}

Endi UserHttpModule da UsersModule ni import qilsak, u modul providerlarida @InjectRepository(User) dan foydalanishimiz mumkin.

TypeScript
users-http.module
1import { Module } from '@nestjs/common';
2import { UsersModule } from './users.module';
3import { UsersService } from './users.service';
4import { UsersController } from './users.controller';
5
6@Module({
7  imports: [UsersModule],
8  providers: [UsersService],
9  controllers: [UsersController]
10})
11export class UserHttpModule {}

Bog'lanishlar

Bog'lanishlar ikki yoki undan ortiq jadval orasida o'rnatiladigan assotsiatsiyalardir. Bog'lanishlar har bir jadvaldagi umumiy maydonlarga asoslanadi, ko'pincha primary va foreign keylarni o'z ichiga oladi.

Bog'lanishlarning uch turi bor:

One-to-oneAsosiy jadvaldagi har bir qator foreign jadvaldagi bitta va faqat bitta mos qatorga ega. Bu bog'lanishni aniqlash uchun @OneToOne() dekoratoridan foydalaning.
One-to-many / Many-to-oneAsosiy jadvaldagi har bir qator foreign jadvaldagi bir yoki bir nechta bog'langan qatorga ega. Bu bog'lanishni aniqlash uchun @OneToMany() va @ManyToOne() dekoratorlaridan foydalaning.
Many-to-manyAsosiy jadvaldagi har bir qator foreign jadvaldagi ko'plab bog'langan qatorga ega va foreign jadvaldagi har bir yozuv asosiy jadvaldagi ko'plab bog'langan qatorga ega. Bu bog'lanishni aniqlash uchun @ManyToMany() dekoratoridan foydalaning.

Entitylarda bog'lanishlarni aniqlash uchun mos dekoratorlar dan foydalaning. Masalan, har bir User da bir nechta foto bo'lishini ko'rsatish uchun @OneToMany() dekoratoridan foydalaning.

TypeScript
user.entity
1import { Entity, Column, PrimaryGeneratedColumn, OneToMany } from 'typeorm';
2import { Photo } from '../photos/photo.entity';
3
4@Entity()
5export class User {
6  @PrimaryGeneratedColumn()
7  id: number;
8
9  @Column()
10  firstName: string;
11
12  @Column()
13  lastName: string;
14
15  @Column({ default: true })
16  isActive: boolean;
17
18  @OneToMany(type => Photo, photo => photo.user)
19  photos: Photo[];
20}
Hint

TypeORM dagi bog'lanishlar haqida batafsil ma'lumot uchun TypeORM hujjatlari ga qarang.

Entitylarni avtomatik yuklash

Entitylarni data source opsiyalari ichidagi entities massiviga qo'lda qo'shish zerikarli bo'lishi mumkin. Bundan tashqari, root moduldan entitylarga murojaat qilish ilova domen chegaralarini buzadi va implementatsiya tafsilotlari boshqa qismlarga sizib chiqishiga sabab bo'ladi. Bu muammoni hal qilish uchun muqobil yechim mavjud. Entitylarni avtomatik yuklash uchun konfiguratsiya obyektidagi autoLoadEntities xossasini (forRoot() metodiga uzatiladigan) true qilib qo'ying, quyida ko'rsatilgandek:

TypeScript
app.module
1import { Module } from '@nestjs/common';
2import { TypeOrmModule } from '@nestjs/typeorm';
3
4@Module({
5  imports: [
6    TypeOrmModule.forRoot({
7      ...
8      autoLoadEntities: true,
9    }),
10  ],
11})
12export class AppModule {}

Bu opsiya berilgach, forFeature() metodi orqali ro'yxatdan o'tkazilgan har bir entity avtomatik ravishda konfiguratsiya obyektidagi entities massiviga qo'shiladi.

Warning

Eslatma: forFeature() orqali ro'yxatdan o'tkazilmagan, faqat entitydan (bog'lanish orqali) referens qilingan entitylar autoLoadEntities sozlamasi orqali qo'shilmaydi.

Entity ta'rifini ajratish

Entity va uning ustunlarini dekoratorlar yordamida bevosita model ichida aniqlashingiz mumkin. Ammo ba'zilar entity va uning ustunlarini alohida fayllarda "entity schemas" yordamida belgilashni afzal ko'radi.

TypeScript
1import { EntitySchema } from 'typeorm';
2import { User } from './user.entity';
3
4export const UserSchema = new EntitySchema<User>({
5  name: 'User',
6  target: User,
7  columns: {
8    id: {
9      type: Number,
10      primary: true,
11      generated: true,
12    },
13    firstName: {
14      type: String,
15    },
16    lastName: {
17      type: String,
18    },
19    isActive: {
20      type: Boolean,
21      default: true,
22    },
23  },
24  relations: {
25    photos: {
26      type: 'one-to-many',
27      target: 'Photo', // the name of the PhotoSchema
28    },
29  },
30});
Warning

error Warning Agar target opsiyasini bersangiz, name opsiyasi qiymati target klass nomi bilan bir xil bo'lishi kerak. Agar target bermasangiz, istalgan nomdan foydalanishingiz mumkin.

Nest Entity kutiladigan istalgan joyda EntitySchema instansiyasidan foydalanishga ruxsat beradi, masalan:

TypeScript
1import { Module } from '@nestjs/common';
2import { TypeOrmModule } from '@nestjs/typeorm';
3import { UserSchema } from './user.schema';
4import { UsersController } from './users.controller';
5import { UsersService } from './users.service';
6
7@Module({
8  imports: [TypeOrmModule.forFeature([UserSchema])],
9  providers: [UsersService],
10  controllers: [UsersController],
11})
12export class UsersModule {}

TypeORM tranzaksiyalari

Ma'lumotlar bazasi tranzaksiyasi - bu ma'lumotlar bazasi boshqaruv tizimida (DBMS) bajariladigan ish birligi bo'lib, boshqa tranzaksiyalardan mustaqil ravishda izchil va ishonchli tarzda ko'rib chiqiladi. Tranzaksiya odatda bazadagi istalgan o'zgarishni anglatadi (batafsil).

TypeORM tranzaksiyalarini boshqarishning ko'plab strategiyalari mavjud. Biz QueryRunner klassidan foydalanishni tavsiya qilamiz, chunki u tranzaksiyani to'liq boshqarish imkonini beradi.

Avval DataSource obyektini odatdagi tartibda klassga inject qilishimiz kerak:

TypeScript
1@Injectable()
2export class UsersService {
3  constructor(private dataSource: DataSource) {}
4}
Hint

DataSource klassi typeorm paketidan import qilinadi.

Endi bu obyekt yordamida tranzaksiya yaratishimiz mumkin.

TypeScript
1async createMany(users: User[]) {
2  const queryRunner = this.dataSource.createQueryRunner();
3
4  await queryRunner.connect();
5  await queryRunner.startTransaction();
6  try {
7    await queryRunner.manager.save(users[0]);
8    await queryRunner.manager.save(users[1]);
9
10    await queryRunner.commitTransaction();
11  } catch (err) {
12    // since we have errors lets rollback the changes we made
13    await queryRunner.rollbackTransaction();
14  } finally {
15    // you need to release a queryRunner which was manually instantiated
16    await queryRunner.release();
17  }
18}
Hint

E'tibor bering, dataSource faqat QueryRunner yaratish uchun ishlatiladi. Biroq bu klassni test qilish uchun butun DataSource obyektini (u bir nechta metodlarni ochib beradi) mock qilish kerak bo'ladi. Shu sababli, yordamchi factory klass (masalan, QueryRunnerFactory)dan foydalanish va tranzaksiyalarni saqlash uchun kerak bo'ladigan metodlarning cheklangan to'plamiga ega interfeys aniqlashni tavsiya qilamiz. Bu yondashuv bu metodlarni mock qilishni juda osonlashtiradi.

Muqobil ravishda, DataSource obyektining transaction metodi bilan callback-uslubidan foydalanishingiz mumkin (batafsil o'qing).

TypeScript
1async createMany(users: User[]) {
2  await this.dataSource.transaction(async manager => {
3    await manager.save(users[0]);
4    await manager.save(users[1]);
5  });
6}

Subscriberlar

TypeORM subscriberlari yordamida siz muayyan entity hodisalarini tinglashingiz mumkin.

TypeScript
1import {
2  DataSource,
3  EntitySubscriberInterface,
4  EventSubscriber,
5  InsertEvent,
6} from 'typeorm';
7import { User } from './user.entity';
8
9@EventSubscriber()
10export class UserSubscriber implements EntitySubscriberInterface<User> {
11  constructor(dataSource: DataSource) {
12    dataSource.subscribers.push(this);
13  }
14
15  listenTo() {
16    return User;
17  }
18
19  beforeInsert(event: InsertEvent<User>) {
20    console.log(`BEFORE USER INSERTED: `, event.entity);
21  }
22}
Warning

Event subscriberlar request-scoped bo'lishi mumkin emas.

Endi UserSubscriber klassini providers massiviga qo'shing:

TypeScript
1import { Module } from '@nestjs/common';
2import { TypeOrmModule } from '@nestjs/typeorm';
3import { User } from './user.entity';
4import { UsersController } from './users.controller';
5import { UsersService } from './users.service';
6import { UserSubscriber } from './user.subscriber';
7
8@Module({
9  imports: [TypeOrmModule.forFeature([User])],
10  providers: [UsersService, UserSubscriber],
11  controllers: [UsersController],
12})
13export class UsersModule {}

Migratsiyalar

Migratsiyalar bazadagi mavjud ma'lumotlarni saqlagan holda, ilovaning ma'lumotlar modeli bilan sxemani sinxron ushlab turish uchun bazani bosqichma-bosqich yangilash imkonini beradi. Migratsiyalarni yaratish, ishga tushirish va qaytarish uchun TypeORM maxsus CLI taqdim etadi.

Migratsiya klasslari Nest ilovasi source kodidan alohida bo'ladi. Ularning hayotiy sikli TypeORM CLI tomonidan boshqariladi. Shu sababli, migratsiyalarda dependency injection va boshqa Nestga xos funksiyalarni ishlata olmaysiz. Migratsiyalar haqida batafsil ma'lumot uchun TypeORM hujjatlaridagi qo'llanmaga qarang.

Bir nechta ma'lumotlar bazasi

Ba'zi loyihalar bir nechta ma'lumotlar bazasi ulanishini talab qiladi. Buni ham ushbu modul orqali amalga oshirish mumkin. Bir nechta ulanish bilan ishlash uchun avval ulanishlarni yarating. Bu holatda data source nomlash majburiy bo'ladi.

Faraz qilaylik, Album entity alohida bazada saqlanadi.

TypeScript
1const defaultOptions = {
2  type: 'postgres',
3  port: 5432,
4  username: 'user',
5  password: 'password',
6  database: 'db',
7  synchronize: true,
8};
9
10@Module({
11  imports: [
12    TypeOrmModule.forRoot({
13      ...defaultOptions,
14      host: 'user_db_host',
15      entities: [User],
16    }),
17    TypeOrmModule.forRoot({
18      ...defaultOptions,
19      name: 'albumsConnection',
20      host: 'album_db_host',
21      entities: [Album],
22    }),
23  ],
24})
25export class AppModule {}
Notice

Agar data source uchun name o'rnatmasangiz, u default deb nomlanadi. E'tibor bering, nomi bo'lmagan yoki bir xil nomdagi bir nechta ulanishlar bo'lmasligi kerak, aks holda ular bir-birini bosib ketadi.

Notice

Agar TypeOrmModule.forRootAsync dan foydalanayotgan bo'lsangiz, data source nomini useFactory dan tashqarida ham berishingiz kerak. Masalan:

TypeOrmModule.forRootAsync({ name: 'albumsConnection', useFactory: ..., inject: ..., }),

Batafsil ma'lumot uchun bu issue ga qarang.

Bu nuqtada User va Album entitylari o'z data source lari bilan ro'yxatdan o'tgan bo'ladi. Bu sozlama bilan, TypeOrmModule.forFeature() metodi va @InjectRepository() dekoratoriga qaysi data source ishlatilishi kerakligini aytishingiz kerak. Agar data source nomini bermasangiz, default data source ishlatiladi.

TypeScript
1@Module({
2  imports: [
3    TypeOrmModule.forFeature([User]),
4    TypeOrmModule.forFeature([Album], 'albumsConnection'),
5  ],
6})
7export class AppModule {}

Berilgan data source uchun DataSource yoki EntityManager ni ham inject qilishingiz mumkin:

TypeScript
1@Injectable()
2export class AlbumsService {
3  constructor(
4    @InjectDataSource('albumsConnection')
5    private dataSource: DataSource,
6    @InjectEntityManager('albumsConnection')
7    private entityManager: EntityManager,
8  ) {}
9}

Shuningdek, istalgan DataSource ni providerlarga inject qilish ham mumkin:

TypeScript
1@Module({
2  providers: [
3    {
4      provide: AlbumsService,
5      useFactory: (albumsConnection: DataSource) => {
6        return new AlbumsService(albumsConnection);
7      },
8      inject: [getDataSourceToken('albumsConnection')],
9    },
10  ],
11})
12export class AlbumsModule {}

Testlash

Ilovani unit testlashda, odatda, bazaga ulanish qilmaslikni xohlaymiz, bu test to'plamlarini mustaqil qiladi va bajarilishini imkon qadar tez qiladi. Ammo klasslarimiz data source (connection) instansiyasidan olinadigan repositorylarga bog'liq bo'lishi mumkin. Buni qanday hal qilamiz? Yechim - mock repositorylar yaratish. Bunga erishish uchun custom providerlar o'rnatamiz. Har bir ro'yxatdan o'tgan repository avtomatik ravishda <EntityName>Repository tokeni bilan ifodalanadi, bu yerda EntityName sizning entity klassingiz nomi.

@nestjs/typeorm paketi getRepositoryToken() funksiyasini taqdim etadi, u berilgan entity asosida tayyor token qaytaradi.

TypeScript
1@Module({
2  providers: [
3    UsersService,
4    {
5      provide: getRepositoryToken(User),
6      useValue: mockRepository,
7    },
8  ],
9})
10export class UsersModule {}

Endi o'rnini bosuvchi mockRepository UsersRepository sifatida ishlatiladi. Istalgan klass @InjectRepository() dekoratori orqali UsersRepository so'rasa, Nest ro'yxatdan o'tgan mockRepository obyektidan foydalanadi.

Async konfiguratsiya

Repository modul opsiyalarini statik emas, asinxron tarzda uzatishni xohlashingiz mumkin. Bu holatda, asinxron konfiguratsiya bilan ishlashning bir nechta usullarini taqdim etadigan forRootAsync() metodidan foydalaning.

Usullardan biri - factory funksiyasidan foydalanish:

TypeScript
1TypeOrmModule.forRootAsync({
2  useFactory: () => ({
3    type: 'mysql',
4    host: 'localhost',
5    port: 3306,
6    username: 'root',
7    password: 'root',
8    database: 'test',
9    entities: [],
10    synchronize: true,
11  }),
12});

Factory funksiyamiz har qanday asinxron provider kabi ishlaydi (masalan, async bo'lishi mumkin va inject orqali bog'liqliklarni qabul qiladi).

TypeScript
1TypeOrmModule.forRootAsync({
2  imports: [ConfigModule],
3  useFactory: (configService: ConfigService) => ({
4    type: 'mysql',
5    host: configService.get('HOST'),
6    port: +configService.get('PORT'),
7    username: configService.get('USERNAME'),
8    password: configService.get('PASSWORD'),
9    database: configService.get('DATABASE'),
10    entities: [],
11    synchronize: true,
12  }),
13  inject: [ConfigService],
14});

Muqobil ravishda, useClass sintaksisidan foydalanishingiz mumkin:

TypeScript
1TypeOrmModule.forRootAsync({
2  useClass: TypeOrmConfigService,
3});

Yuqoridagi konstruktsiya TypeOrmConfigService ni TypeOrmModule ichida instansiyalaydi va createTypeOrmOptions() metodini chaqirib opsiyalar obyektini taqdim etish uchun foydalanadi. Bu TypeOrmConfigService TypeOrmOptionsFactory interfeysini implementatsiya qilishi kerakligini anglatadi, quyida ko'rsatilgandek:

TypeScript
1@Injectable()
2export class TypeOrmConfigService implements TypeOrmOptionsFactory {
3  createTypeOrmOptions(): TypeOrmModuleOptions {
4    return {
5      type: 'mysql',
6      host: 'localhost',
7      port: 3306,
8      username: 'root',
9      password: 'root',
10      database: 'test',
11      entities: [],
12      synchronize: true,
13    };
14  }
15}

TypeOrmModule ichida TypeOrmConfigService ni yaratmasdan, boshqa moduldan import qilingan providerni ishlatish uchun useExisting sintaksisidan foydalaning.

TypeScript
1TypeOrmModule.forRootAsync({
2  imports: [ConfigModule],
3  useExisting: ConfigService,
4});

Bu konstruktsiya useClass bilan bir xil ishlaydi, lekin bitta muhim farqi bor - TypeOrmModule yangi ConfigService instansiyasini yaratish o'rniga import qilingan modullardan mavjud ConfigService ni qayta ishlatish uchun qidiradi.

Hint

name xossasi useFactory, useClass yoki useValue xossalari bilan bir xil darajada aniqlanganiga ishonch hosil qiling. Bu Nestga data source ni mos injection token ostida to'g'ri ro'yxatdan o'tkazishga imkon beradi.

Custom DataSource Factory

useFactory, useClass yoki useExisting orqali asinxron konfiguratsiya bilan birga, ixtiyoriy tarzda dataSourceFactory funksiyasini ko'rsatishingiz mumkin, bu sizga TypeOrmModule data source ni yaratishiga yo'l qo'ymay, o'zingizning TypeORM data source ni taqdim etishga imkon beradi.

dataSourceFactory asinxron konfiguratsiyada useFactory, useClass yoki useExisting orqali sozlangan TypeORM DataSourceOptions ni qabul qiladi va TypeORM DataSource ni Promise sifatida qaytaradi.

TypeScript
1TypeOrmModule.forRootAsync({
2  imports: [ConfigModule],
3  inject: [ConfigService],
4  // Use useFactory, useClass, or useExisting
5  // to configure the DataSourceOptions.
6  useFactory: (configService: ConfigService) => ({
7    type: 'mysql',
8    host: configService.get('HOST'),
9    port: +configService.get('PORT'),
10    username: configService.get('USERNAME'),
11    password: configService.get('PASSWORD'),
12    database: configService.get('DATABASE'),
13    entities: [],
14    synchronize: true,
15  }),
16  // dataSource receives the configured DataSourceOptions
17  // and returns a Promise<DataSource>.
18  dataSourceFactory: async (options) => {
19    const dataSource = await new DataSource(options).initialize();
20    return dataSource;
21  },
22});
Hint

DataSource klassi typeorm paketidan import qilinadi.

Misol

Ishlaydigan misol bu yerda mavjud.

Sequelize integratsiyasi

TypeORM ga muqobil sifatida @nestjs/sequelize paketi bilan Sequelize ORM dan foydalanishingiz mumkin. Bundan tashqari, entitylarni deklarativ tarzda aniqlash uchun qo'shimcha dekoratorlar to'plamini taqdim etadigan sequelize-typescript paketidan foydalanamiz.

Undan foydalanishni boshlash uchun avval kerakli bog'liqliklarni o'rnatamiz. Bu bobda mashhur MySQL relatsion DBMS ni ishlatib ko'rsatamiz, ammo Sequelize PostgreSQL, MySQL, Microsoft SQL Server, SQLite va MariaDB kabi ko'plab relatsion bazalarni qo'llab-quvvatlaydi. Bu bobda ko'rsatiladigan tartib Sequelize qo'llab-quvvatlaydigan istalgan baza uchun bir xil bo'ladi. Siz faqat tanlangan bazangizga mos client API kutubxonalarini o'rnatishingiz kerak bo'ladi.

Terminal
1$ npm install --save @nestjs/sequelize sequelize sequelize-typescript mysql2
2$ npm install --save-dev @types/sequelize

O'rnatish jarayoni tugagach, SequelizeModule ni root AppModule ga import qilamiz.

TypeScript
app.module
1import { Module } from '@nestjs/common';
2import { SequelizeModule } from '@nestjs/sequelize';
3
4@Module({
5  imports: [
6    SequelizeModule.forRoot({
7      dialect: 'mysql',
8      host: 'localhost',
9      port: 3306,
10      username: 'root',
11      password: 'root',
12      database: 'test',
13      models: [],
14    }),
15  ],
16})
17export class AppModule {}

forRoot() metodi Sequelize konstruktori taqdim etadigan barcha konfiguratsiya xossalarini qo'llab-quvvatlaydi (batafsil). Bundan tashqari, quyida tasvirlangan bir nechta qo'shimcha konfiguratsiya xossalari mavjud.

retryAttemptsMa'lumotlar bazasiga ulanishga urinishlar soni (default: 10)
retryDelayUlanishni qayta urinib ko'rishlar orasidagi kechikish (ms) (default: 3000)
autoLoadModelsAgar true bo'lsa, modellar avtomatik yuklanadi (default: false)
keepConnectionAliveAgar true bo'lsa, ilova o'chirilganda ulanish yopilmaydi (default: false)
synchronizeAgar true bo'lsa, avtomatik yuklangan modellar sinxronlashtiriladi (default: true)

Shu ish bajarilgach, Sequelize obyektini butun loyiha bo'ylab inject qilish mumkin bo'ladi (hech qanday modul importini talab qilmasdan), masalan:

TypeScript
app.service
1import { Injectable } from '@nestjs/common';
2import { Sequelize } from 'sequelize-typescript';
3
4@Injectable()
5export class AppService {
6  constructor(private sequelize: Sequelize) {}
7}

Modellar

Sequelize Active Record patternini implementatsiya qiladi. Bu pattern bilan siz model klasslarini bevosita ishlatib, bazaga murojaat qilasiz. Misolni davom ettirish uchun kamida bitta model kerak. User modelini aniqlaymiz.

TypeScript
user.model
1import { Column, Model, Table } from 'sequelize-typescript';
2
3@Table
4export class User extends Model {
5  @Column
6  firstName: string;
7
8  @Column
9  lastName: string;
10
11  @Column({ defaultValue: true })
12  isActive: boolean;
13}
Hint

Mavjud dekoratorlar haqida batafsil bu yerda o'qing.

User model fayli users direktoriyasida joylashadi. Bu direktoriyada UsersModule ga tegishli barcha fayllar bo'ladi. Model fayllarini qayerda saqlashni o'zingiz tanlashingiz mumkin, ammo ularni mos modul direktoriyasida, o'z domen obyektlari yonida saqlashni tavsiya qilamiz.

User modelidan foydalanishni boshlash uchun, uni moduldagi forRoot() metodi opsiyalaridagi models massiviga qo'shib, Sequelize ga bildirishimiz kerak:

TypeScript
app.module
1import { Module } from '@nestjs/common';
2import { SequelizeModule } from '@nestjs/sequelize';
3import { User } from './users/user.model';
4
5@Module({
6  imports: [
7    SequelizeModule.forRoot({
8      dialect: 'mysql',
9      host: 'localhost',
10      port: 3306,
11      username: 'root',
12      password: 'root',
13      database: 'test',
14      models: [User],
15    }),
16  ],
17})
18export class AppModule {}

Endi UsersModule ni ko'rib chiqamiz:

TypeScript
users.module
1import { Module } from '@nestjs/common';
2import { SequelizeModule } from '@nestjs/sequelize';
3import { User } from './user.model';
4import { UsersController } from './users.controller';
5import { UsersService } from './users.service';
6
7@Module({
8  imports: [SequelizeModule.forFeature([User])],
9  providers: [UsersService],
10  controllers: [UsersController],
11})
12export class UsersModule {}

Bu modul forFeature() metodidan foydalanib joriy scope da qaysi modellar ro'yxatdan o'tkazilishini belgilaydi. Shundan so'ng @InjectModel() dekoratori yordamida UsersService ga UserModel ni inject qilishimiz mumkin:

TypeScript
users.service
1import { Injectable } from '@nestjs/common';
2import { InjectModel } from '@nestjs/sequelize';
3import { User } from './user.model';
4
5@Injectable()
6export class UsersService {
7  constructor(
8    @InjectModel(User)
9    private userModel: typeof User,
10  ) {}
11
12  async findAll(): Promise<User[]> {
13    return this.userModel.findAll();
14  }
15
16  findOne(id: string): Promise<User> {
17    return this.userModel.findOne({
18      where: {
19        id,
20      },
21    });
22  }
23
24  async remove(id: string): Promise<void> {
25    const user = await this.findOne(id);
26    await user.destroy();
27  }
28}
Notice

UsersModule ni root AppModule ga import qilishni unutmang.

Agar SequelizeModule.forFeature import qilgan moduldan tashqarida modeldan foydalanmoqchi bo'lsangiz, u yaratgan providerlarni qayta eksport qilishingiz kerak. Buni butun modulni export qilish orqali qilasiz, quyidagicha:

TypeScript
users.module
1import { Module } from '@nestjs/common';
2import { SequelizeModule } from '@nestjs/sequelize';
3import { User } from './user.entity';
4
5@Module({
6  imports: [SequelizeModule.forFeature([User])],
7  exports: [SequelizeModule]
8})
9export class UsersModule {}

Endi UserHttpModule da UsersModule ni import qilsak, u modul providerlarida @InjectModel(User) dan foydalanishimiz mumkin.

TypeScript
users-http.module
1import { Module } from '@nestjs/common';
2import { UsersModule } from './users.module';
3import { UsersService } from './users.service';
4import { UsersController } from './users.controller';
5
6@Module({
7  imports: [UsersModule],
8  providers: [UsersService],
9  controllers: [UsersController]
10})
11export class UserHttpModule {}

Bog'lanishlar

Bog'lanishlar ikki yoki undan ortiq jadval orasida o'rnatiladigan assotsiatsiyalardir. Bog'lanishlar har bir jadvaldagi umumiy maydonlarga asoslanadi, ko'pincha primary va foreign keylarni o'z ichiga oladi.

Bog'lanishlarning uch turi bor:

One-to-oneAsosiy jadvaldagi har bir qator foreign jadvaldagi bitta va faqat bitta mos qatorga ega
One-to-many / Many-to-oneAsosiy jadvaldagi har bir qator foreign jadvaldagi bir yoki bir nechta bog'langan qatorga ega
Many-to-manyAsosiy jadvaldagi har bir qator foreign jadvaldagi ko'plab bog'langan qatorga ega, va foreign jadvaldagi har bir yozuv asosiy jadvaldagi ko'plab bog'langan qatorga ega

Modellarda bog'lanishlarni aniqlash uchun mos dekoratorlar dan foydalaning. Masalan, har bir User da bir nechta foto bo'lishini ko'rsatish uchun @HasMany() dekoratoridan foydalaning.

TypeScript
user.model
1import { Column, Model, Table, HasMany } from 'sequelize-typescript';
2import { Photo } from '../photos/photo.model';
3
4@Table
5export class User extends Model {
6  @Column
7  firstName: string;
8
9  @Column
10  lastName: string;
11
12  @Column({ defaultValue: true })
13  isActive: boolean;
14
15  @HasMany(() => Photo)
16  photos: Photo[];
17}
Hint

Sequelize dagi assotsiatsiyalar haqida batafsil shu bobda o'qing.

Modellarni avtomatik yuklash

Modellarni ulanish opsiyalaridagi models massiviga qo'lda qo'shish zerikarli bo'lishi mumkin. Bundan tashqari, root moduldan modellarga murojaat qilish ilova domen chegaralarini buzadi va implementatsiya tafsilotlari boshqa qismlarga sizib chiqishiga sabab bo'ladi. Bu muammoni hal qilish uchun forRoot() metodiga uzatiladigan konfiguratsiya obyektida autoLoadModels va synchronize xossalarini true qilib, modellarni avtomatik yuklang, quyida ko'rsatilgandek:

TypeScript
app.module
1import { Module } from '@nestjs/common';
2import { SequelizeModule } from '@nestjs/sequelize';
3
4@Module({
5  imports: [
6    SequelizeModule.forRoot({
7      ...
8      autoLoadModels: true,
9      synchronize: true,
10    }),
11  ],
12})
13export class AppModule {}

Bu opsiya berilgach, forFeature() metodi orqali ro'yxatdan o'tkazilgan har bir model avtomatik ravishda konfiguratsiya obyektidagi models massiviga qo'shiladi.

Warning

Eslatma: forFeature() orqali ro'yxatdan o'tkazilmagan, faqat modeldan (assotsiatsiya orqali) referens qilingan modellar qo'shilmaydi.

Sequelize tranzaksiyalari

Ma'lumotlar bazasi tranzaksiyasi - bu ma'lumotlar bazasi boshqaruv tizimida (DBMS) bajariladigan ish birligi bo'lib, boshqa tranzaksiyalardan mustaqil ravishda izchil va ishonchli tarzda ko'rib chiqiladi. Tranzaksiya odatda bazadagi istalgan o'zgarishni anglatadi (batafsil).

Sequelize tranzaksiyalarini boshqarishning ko'plab strategiyalari mavjud. Quyida boshqariladigan tranzaksiyaning (auto-callback) namuna implementatsiyasi keltirilgan.

Avval Sequelize obyektini odatdagi tartibda klassga inject qilishimiz kerak:

TypeScript
1@Injectable()
2export class UsersService {
3  constructor(private sequelize: Sequelize) {}
4}
Hint

Sequelize klassi sequelize-typescript paketidan import qilinadi.

Endi bu obyekt yordamida tranzaksiya yaratishimiz mumkin.

TypeScript
1async createMany() {
2  try {
3    await this.sequelize.transaction(async t => {
4      const transactionHost = { transaction: t };
5
6      await this.userModel.create(
7          { firstName: 'Abraham', lastName: 'Lincoln' },
8          transactionHost,
9      );
10      await this.userModel.create(
11          { firstName: 'John', lastName: 'Boothe' },
12          transactionHost,
13      );
14    });
15  } catch (err) {
16    // Transaction has been rolled back
17    // err is whatever rejected the promise chain returned to the transaction callback
18  }
19}
Hint

E'tibor bering, Sequelize instansiyasi faqat tranzaksiyani boshlash uchun ishlatiladi. Biroq bu klassni test qilish uchun butun Sequelize obyektini (u bir nechta metodlarni ochib beradi) mock qilish kerak bo'ladi. Shu sababli, yordamchi factory klass (masalan, TransactionRunner)dan foydalanish va tranzaksiyalarni saqlash uchun kerak bo'ladigan metodlarning cheklangan to'plamiga ega interfeys aniqlashni tavsiya qilamiz. Bu yondashuv bu metodlarni mock qilishni juda osonlashtiradi.

Migratsiyalar

Migratsiyalar bazadagi mavjud ma'lumotlarni saqlagan holda, ilovaning ma'lumotlar modeli bilan sxemani sinxron ushlab turish uchun bazani bosqichma-bosqich yangilash imkonini beradi. Migratsiyalarni yaratish, ishga tushirish va qaytarish uchun Sequelize maxsus CLI taqdim etadi.

Migratsiya klasslari Nest ilovasi source kodidan alohida bo'ladi. Ularning hayotiy sikli Sequelize CLI tomonidan boshqariladi. Shu sababli, migratsiyalarda dependency injection va boshqa Nestga xos funksiyalarni ishlata olmaysiz. Migratsiyalar haqida batafsil ma'lumot uchun Sequelize hujjatlaridagi qo'llanmaga qarang.

Bir nechta ma'lumotlar bazasi

Ba'zi loyihalar bir nechta ma'lumotlar bazasi ulanishini talab qiladi. Buni ham ushbu modul orqali amalga oshirish mumkin. Bir nechta ulanish bilan ishlash uchun avval ulanishlarni yarating. Bu holatda ulanishlarni nomlash majburiy bo'ladi.

Faraz qilaylik, Album entity alohida bazada saqlanadi.

TypeScript
1const defaultOptions = {
2  dialect: 'postgres',
3  port: 5432,
4  username: 'user',
5  password: 'password',
6  database: 'db',
7  synchronize: true,
8};
9
10@Module({
11  imports: [
12    SequelizeModule.forRoot({
13      ...defaultOptions,
14      host: 'user_db_host',
15      models: [User],
16    }),
17    SequelizeModule.forRoot({
18      ...defaultOptions,
19      name: 'albumsConnection',
20      host: 'album_db_host',
21      models: [Album],
22    }),
23  ],
24})
25export class AppModule {}
Notice

Agar ulanish uchun name o'rnatmasangiz, u default deb nomlanadi. E'tibor bering, nomi bo'lmagan yoki bir xil nomdagi bir nechta ulanishlar bo'lmasligi kerak, aks holda ular bir-birini bosib ketadi.

Bu nuqtada User va Album modellar o'z ulanishlari bilan ro'yxatdan o'tgan bo'ladi. Bu sozlama bilan, SequelizeModule.forFeature() metodi va @InjectModel() dekoratoriga qaysi ulanish ishlatilishi kerakligini aytishingiz kerak. Agar ulanish nomini bermasangiz, default ulanish ishlatiladi.

TypeScript
1@Module({
2  imports: [
3    SequelizeModule.forFeature([User]),
4    SequelizeModule.forFeature([Album], 'albumsConnection'),
5  ],
6})
7export class AppModule {}

Berilgan ulanish uchun Sequelize instansiyasini ham inject qilishingiz mumkin:

TypeScript
1@Injectable()
2export class AlbumsService {
3  constructor(
4    @InjectConnection('albumsConnection')
5    private sequelize: Sequelize,
6  ) {}
7}

Shuningdek, istalgan Sequelize instansiyasini providerlarga inject qilish ham mumkin:

TypeScript
1@Module({
2  providers: [
3    {
4      provide: AlbumsService,
5      useFactory: (albumsSequelize: Sequelize) => {
6        return new AlbumsService(albumsSequelize);
7      },
8      inject: [getDataSourceToken('albumsConnection')],
9    },
10  ],
11})
12export class AlbumsModule {}

Testlash

Ilovani unit testlashda, odatda, bazaga ulanish qilmaslikni xohlaymiz, bu test to'plamlarini mustaqil qiladi va bajarilishini imkon qadar tez qiladi. Ammo klasslarimiz ulanish instansiyasidan olinadigan modellarga bog'liq bo'lishi mumkin. Buni qanday hal qilamiz? Yechim - mock modellar yaratish. Bunga erishish uchun custom providerlar o'rnatamiz. Har bir ro'yxatdan o'tgan model avtomatik ravishda <ModelName>Model tokeni bilan ifodalanadi, bu yerda ModelName sizning model klassingiz nomi.

@nestjs/sequelize paketi berilgan model asosida tayyor token qaytaradigan getModelToken() funksiyasini taqdim etadi.

TypeScript
1@Module({
2  providers: [
3    UsersService,
4    {
5      provide: getModelToken(User),
6      useValue: mockModel,
7    },
8  ],
9})
10export class UsersModule {}

Endi o'rnini bosuvchi mockModel UserModel sifatida ishlatiladi. Istalgan klass @InjectModel() dekoratori orqali UserModel so'rasa, Nest ro'yxatdan o'tgan mockModel obyektidan foydalanadi.

Async konfiguratsiya

SequelizeModule opsiyalarini statik emas, asinxron tarzda uzatishni xohlashingiz mumkin. Bu holatda, asinxron konfiguratsiya bilan ishlashning bir nechta usullarini taqdim etadigan forRootAsync() metodidan foydalaning.

Usullardan biri - factory funksiyasidan foydalanish:

TypeScript
1SequelizeModule.forRootAsync({
2  useFactory: () => ({
3    dialect: 'mysql',
4    host: 'localhost',
5    port: 3306,
6    username: 'root',
7    password: 'root',
8    database: 'test',
9    models: [],
10  }),
11});

Factory funksiyamiz har qanday asinxron provider kabi ishlaydi (masalan, async bo'lishi mumkin va inject orqali bog'liqliklarni qabul qiladi).

TypeScript
1SequelizeModule.forRootAsync({
2  imports: [ConfigModule],
3  useFactory: (configService: ConfigService) => ({
4    dialect: 'mysql',
5    host: configService.get('HOST'),
6    port: +configService.get('PORT'),
7    username: configService.get('USERNAME'),
8    password: configService.get('PASSWORD'),
9    database: configService.get('DATABASE'),
10    models: [],
11  }),
12  inject: [ConfigService],
13});

Muqobil ravishda, useClass sintaksisidan foydalanishingiz mumkin:

TypeScript
1SequelizeModule.forRootAsync({
2  useClass: SequelizeConfigService,
3});

Yuqoridagi konstruktsiya SequelizeConfigService ni SequelizeModule ichida instansiyalaydi va createSequelizeOptions() metodini chaqirib opsiyalar obyektini taqdim etish uchun foydalanadi. Bu SequelizeConfigService SequelizeOptionsFactory interfeysini implementatsiya qilishi kerakligini anglatadi, quyida ko'rsatilgandek:

TypeScript
1@Injectable()
2class SequelizeConfigService implements SequelizeOptionsFactory {
3  createSequelizeOptions(): SequelizeModuleOptions {
4    return {
5      dialect: 'mysql',
6      host: 'localhost',
7      port: 3306,
8      username: 'root',
9      password: 'root',
10      database: 'test',
11      models: [],
12    };
13  }
14}

SequelizeModule ichida SequelizeConfigService ni yaratmasdan, boshqa moduldan import qilingan providerni ishlatish uchun useExisting sintaksisidan foydalaning.

TypeScript
1SequelizeModule.forRootAsync({
2  imports: [ConfigModule],
3  useExisting: ConfigService,
4});

Bu konstruktsiya useClass bilan bir xil ishlaydi, lekin bitta muhim farqi bor - SequelizeModule yangi ConfigService instansiyasini yaratish o'rniga import qilingan modullardan mavjud ConfigService ni qayta ishlatish uchun qidiradi.

Misol

Ishlaydigan misol bu yerda mavjud.