이번엔 꽤나 늦었지만 NestJS 에서 DB 를 연결해보겠습니다.
- Mysql
데이터베이스로는 심플하게 Mysql 을 사용하려고 합니다.
> docker run --name mysql-local -p 3306:3306/tcp -e MYSQL_ROOT_PASSWORD=xxx -d mysql:8
일단 로컬에서 진행하는 만큼 docker 로 간단하게 제 맥북에서 Mysql 8버전 container 를 실행했습니다.
패스워드는 자유롭게 넣어주시면 되겠습니다.
저는 예전부터 사내에서도 데이터베이스 클라이언트로 DBeaver 를 사용해왔습니다.
초기 버전에는 몇가지 버그도 있긴 했으나 어느덧 23버전까지 오면서 많은 부분의 개선이 있었으니 마땅한 툴을 사용하고 있지 않으셨다면
한번 써보시는것도 괜찮을 것 같습니다. 별도 라이센스 구매 같은것도 필요 없으니까요.
DBeaver 내에서 데이터베이스 생성과 관련된 부분은 생략하도록 하겠습니다. 저는 test 라는 이름의 데이터베이스를 생성했습니다.
- TypeORM
ORM 으로는 TypeORM 을 사용하려고 합니다. ORM 은 Object Relational Mapping 의 줄임말로 RDB 데이터를 객체로 매핑해주는
역할을 하며, 우리는 이를 통해 DB 데이터를 객체로서 쉽게 다룰 수 있습니다.
ORM 을 사용함으로써 기존 쿼리 작성에 쏟던 시간을 고스란히 비즈니스 로직에 좀 더 집중할 수 있게 되고 유지 보수에 좀 더 용이하다는 장점이 있지만 오직 이것만으로는 구현하기 어려운 부분들도 있고 코드가 더 복잡해지는 단점이 있을수도 있습니다.
ORM 에 대해선 여기선 이정도로만 보도록 하고 넘어가겠습니다.
Node.JS 진영에서 많이 쓰이는 ORM 으로는 TypeORM, Prisma, Sequelize 등이 있는데 요즘은 Prisma 인기가 좀 많아지는 것
같지만 일단 이번엔 TypeORM 으로 갑니다.
npm install typeorm @nestjs/typeorm mysql2
typeorm, @nestjs/typeorm, mysql2 라이브러리를 설치합니다.
그리고 먼저 config 설정을 해보도록 하겠습니다.
DATABASE_TYPE=mysql
DATABASE_HOST=localhost
DATABASE_PORT=3306
DATABASE_USERNAME=XXX
DATABASE_PASSWORD=XXX
DATABASE_SYNCHRONIZE=true
먼저 config/env 의 .development.env 파일에 위와 같은 설정을 추가합니다.
DATABASE_SYNCHRONIZE 설정은 typeorm 설정의 synchronize 에서 사용하기 위한 값인데, 이를 true 로 지정하면
NestJS 서비스가 실행되고 데이터베이스가 연결될 때 DB 를 NestJS 에 작성된 코드대로 초기화합니다.
production 에선 당연히 사용하면 안되는 옵션이지만, 개발간에는 편의를 위해 true 로 세팅합니다.
import { registerAs } from '@nestjs/config';
export const dbConfig = registerAs('db', () => ({
type: process.env.DATABASE_TYPE,
host: process.env.DATABASE_HOST,
port: process.env.DATABASE_PORT,
username: process.env.DATABASE_USERNAME,
password: process.env.DATABASE_PASSWORD,
synchronize: process.env.DATABASE_SYNCHRONIZE,
}));
config 하위에 dbConfig.ts 파일을 만들고 위와 같이 작성해주었습니다.
이전 포스팅에서 slackConfig 를 별도로 분리할 때 작성했던 것과 같은 요령입니다.
이렇게 하면 이제 어떠한 컴퍼넌트에서도 dbConfig 를 주입받아 사용할 수는 있지만, 별도의 서비스 컴퍼넌트를 하나 만들 필요가
있습니다.
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
...
TypeOrmModule.forRoot({
type: process.env.DATABASE_TYPE,
host: process.env.DATBASE_HOST,
...
}),
],
})
위처럼 typeorm 연결 설정을 app.module.ts 에 넣어줘야 하는데 뭐 저렇게 할수도 있겠습니다만
app.module.ts 에서 환경변수에 접근해 세팅하는 코드를 좀 더 깔끔히 정리하고 싶은 생각이 들어서요.
...
import { dbConfig } from './config/dbConfig';
@Module({
imports: [
AdsModule,
SlackModule,
ConfigModule.forRoot({
envFilePath: path.resolve(
__dirname,
process.env.NODE_ENV === 'production'
? 'config/env/.production.env'
: 'config/env/.development.env',
),
load: [slackConfig, dbConfig],
isGlobal: true,
validationSchema,
}),
],
controllers: [],
providers: [],
})
export class AppModule {}
먼저 app.module.ts 에서 ConfigMoudle load 부분에 dbConfig 도 추가해줍니다.
import { Injectable, Inject } from '@nestjs/common';
import { ConfigType } from '@nestjs/config';
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
import { dbConfig } from './dbConfig';
import * as path from 'path';
@Injectable()
export class DbConfigService {
constructor(
@Inject(dbConfig.KEY) private config: ConfigType<typeof dbConfig>,
) {}
public getTypeORMConfig(): TypeOrmModuleOptions {
const type: any = this.config.type;
return {
type,
host: this.config.host,
port: Number(this.config.port),
username: this.config.username,
password: this.config.password,
database: 'test',
logging: false,
entities: [path.join(__dirname, '/../**/*.entity{.ts,.js}')],
synchronize: this.config.synchronize === 'true',
};
}
}
그리고 config 하위에 dbConfig.service.ts 파일을 만들어 위처럼 작성해주었습니다.
위에서 설정한대로 dbConfig 자체는 이미 주입해서 사용할 수 있으니 DbConfigService 는 dbConfig 를 주입받아
ORM 설정에 필요한 config 를 반환해주는 메서드를 작성했습니다.
...
import { TypeOrmModule } from '@nestjs/typeorm';
import { DbConfigService } from './config/dbConfig.service';
@Module({
imports: [
...
TypeOrmModule.forRootAsync({
useFactory: (configService: DbConfigService) =>
configService.getTypeORMConfig(),
extraProviders: [DbConfigService],
inject: [DbConfigService],
}),
],
controllers: [],
providers: [],
})
export class AppModule {}
변경된 app.module.ts 입니다. TypeOrmModule 을 통해 데이터베이스 연결 설정을 할 때 useFactory 를 사용해
동적으로 모듈을 생성할 수 있는데, 여기서 작성했던 DbConfigService 를 사용하는 것입니다.
이 상태로 앱을 띄우면 뭔가 에러가 나는 부분은 없지만 아무래도 실제 확인을 위해선 synchronize 옵션도 true 로 설정을 한 상태니
entity 를 하나 작성해 실제 테이블이 만들어지는지를 확인해보도록 하겠습니다.
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity('Ad')
export class Ad {
@PrimaryGeneratedColumn()
id: number;
@Column({ length: 2, name: 'ad_type' })
adType: string;
@Column({ length: 10 })
title: string;
@Column({ length: 60 })
thumbnail: string;
@Column({ length: 2 })
country: string;
}
기존 ads/entities 디렉토리는 원래 있었고, 여기에 ad.entity.ts 파일만 있고 내용은 비어있었는데 위와 같이 채워봅니다.
@Entity 데코레이터를 통해 이 클래스가 Entity 이며 RDB 이기 때문에 Table 임을 표시합니다.
@PrimaryGeneratedColumn 데코레이터를 통해 우리가 DB 설정시 PK 에서 자주 사용하는 auto generated 컬럼임을 나타냈고
나머지 컬럼들 또한 @Column 데코레이터를 통해 보이는 그대로입니다.
이제 앱을 띄운 후 DBeaver 를 통해 test database 를 확인해봅니다.
entity 코드에서 정의한대로의 컬럼을 포함한 Ad 테이블이 생성된 것을 볼 수 있습니다.
물론 위에 적었던 것처럼 production 에서는 synchronize 옵션을 사용하지 않기 때문에 이렇게 운영할 수는 없지만,
TypeORM 설정이 잘 동작하는 것은 확인할 수 있었습니다.
이번엔 ORM 설정 위주로 보느라 테이블 구성을 그냥 DTO 그대로 따라오다보니 별도의 인덱스 설정같은것도 들어가지 않았는데,
다음 포스팅에서 다시 테이블 설정을 하면서 기존의 Service 에 Repository 를 추가하는 내용까지 담아보도록 하겠습니다.
'Backend > NestJS' 카테고리의 다른 글
NestJS - 대충 서비스 만들어보기 (11) (0) | 2023.05.20 |
---|---|
NestJS - 대충 서비스 만들어보기 (10) (0) | 2023.05.13 |
NestJS - 대충 서비스 만들어보기 (8) (0) | 2023.05.01 |
NestJS - 대충 서비스 만들어보기 (7) (0) | 2023.04.23 |
NestJS - 대충 서비스 만들어보기 (6) (0) | 2023.04.15 |