Database
Nest는 모든 데이터베이스로 모험을 시작하는 데 필요한 상용구를 줄이기 위해 @nestjs/typeorm
패키지를 사용할 준비가 되어 있습니다. TypeORM 은 지금까지 사용 가능한 가장 성숙한 ORM (Object Relational Mapper)이므로 선택했습니다. TypeScript로 작성되었으므로 Nest 프레임 워크에서 잘 작동합니다.
먼저 필요한 모든 종속성을 설치해야합니다.
Copy $ npm install --save @nestjs/typeorm typeorm mysql
info 알림 이 장에서는 MySQL 데이터베이스를 사용하지만 TypeORM 은 PostgreSQL, SQLite 및 MongoDB (NoSQL)와 같은 다양한 데이터베이스를 지원합니다.
설치 과정이 완료되면 TypeOrmModule
을 루트 ApplicationModule
로 가져올 수 있습니다.
Copy @@ 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
파일을 만들 수 있습니다.
Copy {
"type": "mysql",
"host": "localhost",
"port": 3306,
"username": "root",
"password": "root",
"database": "test",
"entities": ["src/**/*.entity{.ts,.js}"],
"synchronize": true
}
그런 다음 간단히 괄호를 비워 둘 수 있습니다.
Copy @@filename(app.module)
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [TypeOrmModule.forRoot()],
})
export class ApplicationModule {}
그런 다음 Connection
및 EntityManager
를 사용하여 다음과 같이 전체 프로젝트에 다른 모듈을 가져 오지 않고 삽입할 수 있습니다.
Copy @@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
엔티티를 재사용 할 것입니다.
Copy @@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
을 보겠습니다:
Copy @@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()
데코레이터를 사용하여 PhotoRepository
를 PhotoService
에 주입할 수 있습니다.
Copy @@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
엔티티가 있고 각각 자체 데이터베이스에 저장되어 있다고 가정하십시오.
Copy 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
, Person
및 Album
엔티티가 자체 연결에 등록되어 있습니다. 이 설정에서는TypeOrmModule.forFeature()
함수와 @InjectRepository()
데코레이터에게 어떤 연결을 사용해야 하는지 알려 주어야 합니다. 연결 이름을 전달하지 않으면 default
연결이 사용됩니다.
Copy @Module({
imports: [
TypeOrmModule.forFeature([Photo]),
TypeOrmModule.forFeature([Person], 'personsConnection'),
TypeOrmModule.forFeature([Album], 'albumsConnection'),
],
})
export class ApplicationModule {}
주어진 연결에 대해 Connection
또는 EntityManager
를 삽입 할 수도 있습니다.
Copy @Injectable()
export class PersonService {
constructor(
@InjectConnection('personsConnection')
private readonly connection: Connection,
@InjectEntityManager('personsConnection')
private readonly entityManager: EntityManager,
) {}
}
Testing
응용 프로그램을 단위 테스트하는 경우 일반적으로 데이터베이스 연결을 피하여 테스트 슈트를 독립적으로 만들고 실행 프로세스를 가능한 한 빨리 만듭니다. 그러나 클래스는 연결 인스턴스에서 가져온 저장소에 따라 달라질 수 있습니다. 그럼 뭐야? 해결책은 가짜 저장소를 만드는 것입니다. 이를 달성하기 위해 커스텀 공급자 를 설정해야 합니다. 실제로 등록된 각 저장소는EntityNameRepository
토큰으로 표시됩니다. 여기서 EntityName
은 엔터티 클래스의 이름입니다.
@nestjs/typeorm
패키지는 주어진 엔티티를 기반으로 준비된 토큰을 반환하는 getRepositoryToken()
함수를 제공합니다.
Copy @Module({
providers: [
PhotoService,
{
provide: getRepositoryToken(Photo),
useValue: mockRepository,
},
],
})
export class PhotoModule {}
이제 하드 코딩 된 mockRepository
가 PhotoRepository
로 사용됩니다. 공급자가 @InjectRepository()
데코레이터를 사용하여 PhotoRepository
를 요청할 때마다 Nest는 등록 된 mockRepository
객체를 사용합니다.
Custom repository
TypeORM은 사용자 정의 저장소 라는 기능을 제공합니다. 이에 대한 자세한 내용은 여기 페이지를 방문하십시오. 기본적으로 사용자 정의 저장소를 사용하면 기본 저장소 클래스를 확장하고 몇 가지 특수한 방법으로 이를 강화할 수 있습니다.
커스텀 저장소를 만들려면 @EntityRepository()
데코레이터를 사용하고 Repository
클래스를 확장하십시오.
Copy @EntityRepository(Author)
export class AuthorRepository extends Repository<Author> {}
info 힌트 @EntityRepository()
와 Repository
는 모두typeorm
패키지에서 나옵니다.
클래스가 생성되면 다음 단계는 인스턴스화 책임을 Nest에 넘겨주는 것입니다. 이를 위해 AuthorRepository
클래스를 TypeOrm.forFeature()
메소드에 전달해야 합니다.
Copy @Module({
imports: [TypeOrmModule.forFeature([AuthorRepository])],
controller: [AuthorController],
providers: [AuthorService],
})
export class AuthorModule {}
그런 다음 다음 구성을 사용하여 저장소를 주입하십시오.
Copy @Injectable()
export class AuthorService {
constructor(private readonly authorRepository: AuthorRepository) {}
}
Async configuration
모듈 옵션을 미리 전달하는 대신 비동기식으로 전달하려는 경우가 종종 있습니다. 이 경우 비동기 데이터를 처리하는 몇 가지 다양한 방법을 제공하는 forRootAsync()
메서드를 사용하십시오.
가능한 첫 번째 방법은 팩토리 기능을 사용하는 것입니다.
Copy TypeOrmModule.forRootAsync({
useFactory: () => ({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'root',
database: 'test',
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true,
}),
});
분명히, 우리 팩토리는 다른 모든 것처럼 행동합니다 ( 비동기(aync)
일 수도 있고 주입(inject)
을 통해 의존성을 주입할 수도 있습니다).
Copy 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],
});
또는 팩토리 대신 클래스를 사용할 수 있습니다.
Copy TypeOrmModule.forRootAsync({
useClass: TypeOrmConfigService,
});
위의 구성은 TypeOrmModule
내에서 TypeOrmConfigService
를 인스턴스화 하고이를 활용하여 옵션 개체를 만듭니다. TypeOrmConfigService
는 TypeOrmOptionsFactory
인터페이스를 구현해야 합니다.
Copy @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
구문을 사용할 수 있습니다.
Copy TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useExisting: ConfigService,
});
하나의 중요한 차이점으로 useClass
와 동일하게 작동합니다. TypeOrmModule
은 가져온 모듈을 조회하여 이미 생성된 ConfigService
를 자체적으로 인스턴스화하는 대신 재사용합니다.
Example
실제 예제는 여기 에서 볼 수 있습니다.