Database

Database

Nest는 모든 데이터베이스로 모험을 시작하는 데 필요한 상용구를 줄이기 위해 @nestjs/typeorm 패키지를 사용할 준비가 되어 있습니다. TypeORM은 지금까지 사용 가능한 가장 성숙한 ORM (Object Relational Mapper)이므로 선택했습니다. TypeScript로 작성되었으므로 Nest 프레임 워크에서 잘 작동합니다.

먼저 필요한 모든 종속성을 설치해야합니다.

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

info 알림 이 장에서는 MySQL 데이터베이스를 사용하지만 TypeORM은 PostgreSQL, SQLite 및 MongoDB (NoSQL)와 같은 다양한 데이터베이스를 지원합니다.

설치 과정이 완료되면 TypeOrmModule을 루트 ApplicationModule로 가져올 수 있습니다.

@@filename(app.module)
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'root',
      database: 'test',
      entities: [__dirname + '/**/*.entity{.ts,.js}'],
      synchronize: true,
    }),
  ],
})
export class ApplicationModule {}

forRoot()메소드는 TypeORM 패키지에서 createConnection()과 동일한 구성 객체를 받습니다. 또한forRoot()에 무엇이든 전달하는 대신 프로젝트 루트 디렉토리에 ormconfig.json 파일을 만들 수 있습니다.

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

그런 다음 간단히 괄호를 비워 둘 수 있습니다.

@@filename(app.module)
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [TypeOrmModule.forRoot()],
})
export class ApplicationModule {}

그런 다음 ConnectionEntityManager를 사용하여 다음과 같이 전체 프로젝트에 다른 모듈을 가져 오지 않고 삽입할 수 있습니다.

@@filename(app.module)
import { Connection } from 'typeorm';

@Module({
  imports: [TypeOrmModule.forRoot(), PhotoModule],
})
export class ApplicationModule {
  constructor(private readonly connection: Connection) {}
}
@@switch
import { Connection } from 'typeorm';

@Dependencies(Connection)
@Module({
  imports: [TypeOrmModule.forRoot(), PhotoModule],
})
export class ApplicationModule {
  constructor(connection) {
    this.connection = connection;
  }
}

Repository pattern

TypeORM은 리포지토리 디자인 패턴을 지원하므로 각 엔터티에는 자체 리포지토리가 있습니다. 이 리포지토리는 데이터베이스 연결에서 얻을 수 있습니다.

첫째, 우리는 최소한 하나의 엔티티가 필요합니다. 공식 문서에서 Photo엔티티를 재사용 할 것입니다.

@@filename(photo.entity)
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class Photo {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ length: 500 })
  name: string;

  @Column('text')
  description: string;

  @Column()
  filename: string;

  @Column('int')
  views: number;

  @Column()
  isPublished: boolean;
}

Photo 엔티티는 photo 디렉토리에 속합니다. 이 디렉토리는 PhotoModule을 나타냅니다. 모델 파일을 어디에 보관할지 결정해야 합니다. 우리의 관점에서, 해당 모듈 디렉토리에서 도메인 근처에 유지하는 가장 좋은 방법입니다.

PhotoModule을 보겠습니다:

@@filename(photo.module)
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { PhotoService } from './photo.service';
import { PhotoController } from './photo.controller';
import { Photo } from './photo.entity';

@Module({
  imports: [TypeOrmModule.forFeature([Photo])],
  providers: [PhotoService],
  controllers: [PhotoController],
})
export class PhotoModule {}

이 모듈은 forFeature()메소드를 사용하여 현재 범위에 등록할 리포지토리를 정의합니다. 덕분에@InjectRepository()데코레이터를 사용하여 PhotoRepositoryPhotoService에 주입할 수 있습니다.

@@filename(photo.service)
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Photo } from './photo.entity';

@Injectable()
export class PhotoService {
  constructor(
    @InjectRepository(Photo)
    private readonly photoRepository: Repository<Photo>,
  ) {}

  findAll(): Promise<Photo[]> {
    return this.photoRepository.find();
  }
}
@@switch
import { Injectable, Dependencies } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Photo } from './photo.entity';

@Injectable()
@Dependencies(InjectRepository(Photo))
export class PhotoService {
  constructor(photoRepository) {
    this.photoRepository = photoRepository;
  }

  findAll() {
    return this.photoRepository.find();
  }
}

warning 알림 PhotoModule을 루트 ApplicationModule로 가져 오는 것을 잊지 마십시오.

Multiple databases

일부 프로젝트에는 여러 개의 데이터베이스 연결이 필요할 수 있습니다. 다행히도 이 모듈을 사용하여 이 작업을 수행할 수도 있습니다. 여러 연결로 작업하려면 가장 먼저해야 할 일은 해당 연결을 만드는 것입니다. 이 경우 연결 이름이 필수가 됩니다.

Person 엔티티와 Album 엔티티가 있고 각각 자체 데이터베이스에 저장되어 있다고 가정하십시오.

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

@Module({
  imports: [
    TypeOrmModule.forRoot({
      ...defaultOptions,
      host: 'photo_db_host',
      entities: [Photo],
    }),
    TypeOrmModule.forRoot({
      ...defaultOptions,
      name: 'personsConnection',
      host: 'person_db_host',
      entities: [Person],
    }),
    TypeOrmModule.forRoot({
      ...defaultOptions,
      name: 'albumsConnection',
      host: 'album_db_host',
      entities: [Album],
    }),
  ],
})
export class ApplicationModule {}

warning 알림 연결에 name을 설정하지 않으면 이름이 default로 설정됩니다. 이름이 없거나 이름이 같은 여러 개의 연결이 없어야 합니다. 그렇지 않으면 단순히 무시됩니다.

이 시점에서 각각의 Photo, PersonAlbum 엔티티가 자체 연결에 등록되어 있습니다. 이 설정에서는TypeOrmModule.forFeature() 함수와 @InjectRepository() 데코레이터에게 어떤 연결을 사용해야 하는지 알려 주어야 합니다. 연결 이름을 전달하지 않으면 default 연결이 사용됩니다.

@Module({
  imports: [
    TypeOrmModule.forFeature([Photo]),
    TypeOrmModule.forFeature([Person], 'personsConnection'),
    TypeOrmModule.forFeature([Album], 'albumsConnection'),
  ],
})
export class ApplicationModule {}

주어진 연결에 대해 Connection 또는 EntityManager를 삽입 할 수도 있습니다.

@Injectable()
export class PersonService {
  constructor(
    @InjectConnection('personsConnection')
    private readonly connection: Connection,
    @InjectEntityManager('personsConnection')
    private readonly entityManager: EntityManager,
  ) {}
}

Testing

응용 프로그램을 단위 테스트하는 경우 일반적으로 데이터베이스 연결을 피하여 테스트 슈트를 독립적으로 만들고 실행 프로세스를 가능한 한 빨리 만듭니다. 그러나 클래스는 연결 인스턴스에서 가져온 저장소에 따라 달라질 수 있습니다. 그럼 뭐야? 해결책은 가짜 저장소를 만드는 것입니다. 이를 달성하기 위해 커스텀 공급자를 설정해야 합니다. 실제로 등록된 각 저장소는EntityNameRepository토큰으로 표시됩니다. 여기서 EntityName은 엔터티 클래스의 이름입니다.

@nestjs/typeorm 패키지는 주어진 엔티티를 기반으로 준비된 토큰을 반환하는 getRepositoryToken() 함수를 제공합니다.

@Module({
  providers: [
    PhotoService,
    {
      provide: getRepositoryToken(Photo),
      useValue: mockRepository,
    },
  ],
})
export class PhotoModule {}

이제 하드 코딩 된 mockRepositoryPhotoRepository로 사용됩니다. 공급자가 @InjectRepository() 데코레이터를 사용하여 PhotoRepository를 요청할 때마다 Nest는 등록 된 mockRepository 객체를 사용합니다.

Custom repository

TypeORM은 사용자 정의 저장소라는 기능을 제공합니다. 이에 대한 자세한 내용은 여기 페이지를 방문하십시오. 기본적으로 사용자 정의 저장소를 사용하면 기본 저장소 클래스를 확장하고 몇 가지 특수한 방법으로 이를 강화할 수 있습니다.

커스텀 저장소를 만들려면 @EntityRepository()데코레이터를 사용하고 Repository 클래스를 확장하십시오.

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

info 힌트 @EntityRepository()Repository는 모두typeorm 패키지에서 나옵니다.

클래스가 생성되면 다음 단계는 인스턴스화 책임을 Nest에 넘겨주는 것입니다. 이를 위해 AuthorRepository 클래스를 TypeOrm.forFeature() 메소드에 전달해야 합니다.

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

그런 다음 다음 구성을 사용하여 저장소를 주입하십시오.

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

Async configuration

모듈 옵션을 미리 전달하는 대신 비동기식으로 전달하려는 경우가 종종 있습니다. 이 경우 비동기 데이터를 처리하는 몇 가지 다양한 방법을 제공하는 forRootAsync()메서드를 사용하십시오.

가능한 첫 번째 방법은 팩토리 기능을 사용하는 것입니다.

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

분명히, 우리 팩토리는 다른 모든 것처럼 행동합니다 ( 비동기(aync)일 수도 있고 주입(inject)을 통해 의존성을 주입할 수도 있습니다).

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

또는 팩토리 대신 클래스를 사용할 수 있습니다.

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

위의 구성은 TypeOrmModule내에서 TypeOrmConfigService를 인스턴스화 하고이를 활용하여 옵션 개체를 만듭니다. TypeOrmConfigServiceTypeOrmOptionsFactory 인터페이스를 구현해야 합니다.

@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,
    };
  }
}

TypeOrmModule 내에 TypeOrmConfigService가 작성되는 것을 방지하고 다른 모듈에서 가져온 제공자를 사용하려면 useExisting 구문을 사용할 수 있습니다.

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

하나의 중요한 차이점으로 useClass와 동일하게 작동합니다. TypeOrmModule은 가져온 모듈을 조회하여 이미 생성된 ConfigService를 자체적으로 인스턴스화하는 대신 재사용합니다.

Example

실제 예제는 여기에서 볼 수 있습니다.

Last updated