Javasciprt/NestJS

[TypeORM] Migration으로 DB 변경하기

씬프 2024. 4. 23. 20:24
반응형

어쩌다 시작하게 되었는가?

서비스 중인 웹 서비스에 백엔드 관련 추가 개발 기능을 배포할 일이 생겼다.

추가 개발과 관련해서 DB 테이블에 스키마 변경이 있었다.

이전에 DB 스키마를 변경할 때, TypeORM 옵션에서 synchronize 옵션을 true로 바꿔 변경했다가 데이터가 날아간 경험이 있었다.

TypeORM에서도 해당 방법으로 프로덕션 DB의 스키마를 변경하는 것을 추천하지 않는다.

 

This option automatically syncs your database tables with the given entities each time you run this code. This option is perfect during development, but in production you may not want this option to be enabled.

 

다음에 배포할 땐, TypeORM Migration을 통해 변경해야겠다고 생각했다.

그 날이 되었다.

 

TypeORM CLI

TypeORM CLI 명령어를 사용할 땐, 프로젝트 디렉터리에서 node ./node_modules/typeorm/cli.js [command]를 통해 사용한다.

TypeORM Migration과 관련해 사용한 명령어는 다음과 같다.

  • migration:create: Migration에 사용되는 DDL을 정의하는 파일을 생성한다.
  • migration:run: Migration을 진행한다.
  • migration:revert: 진행된 Migration을 취소한다.

package.json에 관련된 명령어를 미리 script로 정의해놓으면 편하게 사용할 수 있다.

{
  // 다른 내용
  "scripts": {
    "migration:create": "node ./node_modules/typeorm/cli.js migration:create",
    "migration:run": "yarn build && node ./node_modules/typeorm/cli.js migration:run",
    "migration:revert": "yarn build && node ./node_modules/typeorm/cli.js migration:revert"
  }
  // ...
}

 

TypeORM Migration

 

yarn migration:create path/migration/directory/migration-name 을 통해 Migration 파일을 생성한다.

해당 명령어를 사용하면 path/migration/directory 디렉터리 내에 migration-name 이름을 가진 마이그레이션 관련 파일이 생성된다.

import { MigrationInterface, QueryRunner } from "typeorm";

export class AddTable1713870154590 implements MigrationInterface {

    public async up(queryRunner: QueryRunner): Promise<void> {
    }

    public async down(queryRunner: QueryRunner): Promise<void> {
    }

}

 

위와 같은 기본 코드 구조를 가진 파일이 생성된다.

up 함수에 migration을 진행했을 때 변경되는 DB 스키마 관련 DDL을, down 함수에 migration을 취소했을 때 변경되어야 할 DB 스키마 관련 DDL을 작성한다.

 

만약 유저 테이블에 휴대전화 번호 관련 컬럼을 추가하는 스키마 변경이 있다고 할 때,

import { MigrationInterface, QueryRunner } from "typeorm";

export class AddTable1713870154590 implements MigrationInterface {

    public async up(queryRunner: QueryRunner): Promise<void> {
    	const column = new TableColumn({ name: 'phone', type: 'varchar(20)' });
    	await queryRunner.addColumn('users', column);
    }

    public async down(queryRunner: QueryRunner): Promise<void> {
    }

}

 

위와 같이 테이블에 컬럼을 추가하는 코드를 작성하면 된다.

물론 직접 DDL 쿼리를 작성할 수 있다.

import { MigrationInterface, QueryRunner } from "typeorm";

export class AddTable1713870154590 implements MigrationInterface {

    public async up(queryRunner: QueryRunner): Promise<void> {
    	await queryRunner.query(`
            ALTER TABLE users ADD COLUMN phone varchar(20)
    	`);
    }

    public async down(queryRunner: QueryRunner): Promise<void> {
    }

}

 

Migration을 취소할 때를 대비해 down 함수도 작성한다.

import { MigrationInterface, QueryRunner } from "typeorm";

export class AddTable1713870154590 implements MigrationInterface {

    public async up(queryRunner: QueryRunner): Promise<void> {
    	const column = new TableColumn({ name: 'phone', type: 'varchar(20)' });
    	await queryRunner.addColumn('users', column);
    }

    public async down(queryRunner: QueryRunner): Promise<void> {
    	await queryRunner.dropColumn('users', 'phone');
    }

}

 

DDL과 관련된 내용은 TypeORM에 Migrations 관련 문서를 확인하면 더 다양하게 확인할 수 있다.

 

Migrations | typeorm

Once you get into production you'll need to synchronize model changes into the database. Typically, it is unsafe to use synchronize: true for schema synchronization on production once you get data in your database. Here is where migrations come to help. A

orkhan.gitbook.io

 

이제 migration:run, migration:revert를 실행하면 Migration이 진행되지 않는다.

 

Datasource

migration:run 명령어를 실행하면,

필수 인수가 주어지지 않았습니다 : dataSource

 

라는 문구를 보게 된다.

 

Migration할 때 사용할 DB Connection이 필요했다.

 

export const dbdatasource: DataSourceOptions = {
  type: 'mysql',
  host: process.env.DB_HOST,
  port: +process.env.DB_PORT,
  username: process.env.DB_USERNAME,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_DATABASE,
  entities: [__dirname + '/**/*.entity{.ts,.js}'],
  synchronize: false,
  migrations: ['dist/@database/migrations/*{.ts,.js}'],
};

const dataSource = new DataSource(dbdatasource);

export default dataSource;

 

이렇게 DataSource를 생성하는 코드를 작성했다.

이 파일을 TS 환경에서 실행할 수 있지만, 나는 빌드 이후 JS 파일로 사용하는 것을 선택했다.

 

NestJS를 빌드하면 dist 디렉터리에 src 디렉터리를 기반으로 빌드된 JS 파일이 존재한다.

TS 파일로 위와 같이 DataSource를 정의하고 빌드하면 JS 파일로 해당 DataSource 파일이 생성된다.

이를 migration:run, migration:revert에 파라미터로 전달했다.

 

src/@database/data-source.ts 파일을 생성하고 위 코드를 작성했다.
그리고 yarn build를 통해 빌드를 진행하고, 진행 후 생성된 dist/@database/data-source.js 파일을 migration:run에 파라미터로 전달한다.

 

"migration:run": "yarn build && node ./node_modules/typeorm/cli.js migration:run -d dist/@database/data-source.js",
"migration:revert": "yarn build && node ./node_modules/typeorm/cli.js migration:revert -d dist/@database/data-source.js"

 

이제 migration:run 명령어를 실행하면 변경된 DB 스키마가 적용된다.

'Javasciprt > NestJS' 카테고리의 다른 글

[NestJS] TypeORM naming strategy  (0) 2023.12.26
[NestJS] Request 객체에서 IP 정보 가져오기  (0) 2023.07.28