TypeORM 마이그레이션

Oneik·2024년 6월 18일
0
post-thumbnail

마이그레이션이 필요한 이유

기존 프로젝트에서는 synchronize: true 설정을 통해서 데이터베이스 스키마를 설정하였다.

문제는 위와 같은 설정을 한다면 애플리케이션이 시작될 때 TypeORM이 엔티티를 검사하고, 현재 데이터베이스 스키마와 비교한 후, 변경된 부분이 있다면 자동으로 수정한다는 것이다.

개발 환경에서는 즉각적인 피드백을 받을 수 있기 때문에, 유용하게 사용할 수 있지만 프로덕션 환경에서는 의도치 않은 데이터 손실이 발생할 수 있기 때문에, 위와 같은 설정은 지양해야만 한다.

마이그레이션의 장점

안정성

프로젝트는 혼자서 작업하는게 아니다. 여러 개발자가 동시에 작업할 때, 마이그레이션을 사용한다면 데이터베이스 관리자를 통해 모든 변경 사항을 사전에 검토하고 승인할 수 있어 안정성을 높일 수 있다.

버전 관리

누가 어떤 변경을 했는지 쉽게 파악할 수 있고, 각 마이그레이션 파일에는 변경된 날짜와 시간이 포함되어 있기 때문에, 이를 통해 변경 사항이 언제 적용되었는지도 확인할 수 있다.

또한 변경 사항을 적용한 후 문제가 발생했을 때, 개발자가 쉽게 이전 상태로 되돌릴 수 있다.

마이그레이션 사용하기

dataSourceOptions 설정하기

// datasource.config.ts
import { DataSource, DataSourceOptions } from 'typeorm';
import { SnakeNamingStrategy } from 'typeorm-naming-strategies';
import * as dotenv from 'dotenv';
import * as path from 'path';

dotenv.config({
  path: process.env.NODE_ENV === 'production' ? '.production.env' : '.development.env',
});

const entityPath = path.join(__dirname, '../entities/*/*.entity.{js,ts}');
const migrationPath = path.join(__dirname, '../migrations/*.{js,ts}');

export const dataSourceOptions: DataSourceOptions = {
  type: 'mysql',
  host: process.env.DB_HOST,
  port: parseInt(process.env.DB_PORT),
  username: process.env.DB_USERNAME,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_DATABASE,
  entities: [entityPath],
  synchronize: false,
  migrationsRun: false,
  namingStrategy: new SnakeNamingStrategy(),
  migrationsTableName: 'migrations',
  migrations: [migrationPath],
  logging: process.env.NODE_ENV === 'production' ? ['error'] : true,
};

export const MysqlDataSource = new DataSource(dataSourceOptions);

기존 설정에서는 서버가 실행될 때, 데이터베이스와 연동할 수 있었기 때문에, 동적으로 환경 변수를 설정하기 위해서 ConfigService를 사용하였다.

그러나, migration을 실행하기 위해서는 서버가 실행되기 전에 typeorm-cli를 통해 적용해야만 했다.

따라서, dotenv를 이용하여 NODE_ENV에 설정되어 있는 값에 따라 환경 변수를 가져와 사용할 수 있도록 하였다.

dataSourceOptions를 통해 데이터베이스와 연동하기

// typeorm.module.ts
@Module({
  imports: [TypeOrmModule.forRoot(dataSourceOptions)]
  ...
})

export class TypeOrmModule {}

기존에 데이터베이스와 연동하기 위해 작성해놓은 코드가 존재했지만, 데이터베이스의 설정을 일관적으로 유지하기 위해 작성해둔 dataSourceOptions를 가져와 적용하였다.

TypeORM 마이그레이션을 위한 CLI 설정

"scripts": {

	...
	
	"typeorm": "node -r ts-node/register ./node_modules/typeorm/cli.js",
	"typeorm:dev": "cross-env NODE_ENV=development node -r ts-node/register ./node_modules/typeorm/cli.js -d src/configs/datasource.config.ts",
	"typeorm:prod": "cross-env NODE_ENV=production node -r ts-node/register ./node_modules/typeorm/cli.js -d src/configs/datasource.config.ts"
},

TypeScript로 작성된 파일은 Node.js 환경에서 직접 실행할 수 없다. 따라서, ts-node를 사용하여 TypeScript로 작성된 파일을 직접 실행할 수 있도록 하였다.

또한 TypeORM CLI를 이용해서 데이터베이스 연결 설정 파일을 통해 마이그레이션할 수 있도록 스크립트를 작성하였다.

마이그레이션 파일 생성 명령어

// 명령어
npm run typeorm migration:create ./src/{migrations-dir}/{filename}

// 명령어 예시
npm run typeorm migration:create ./src/migrations/CreateUserTable

마이그레이션 파일 작성

import { MigrationInterface, QueryRunner } from 'typeorm';

export class CreateUserTable1718647700144 implements MigrationInterface {
  name = 'CreateUserTable1718647700144';

  public async up(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(`
      CREATE TABLE IF NOT EXISTS user (
        id            INT            NOT NULL AUTO_INCREMENT PRIMARY KEY                COMMENT 'PK',
        email         VARCHAR(50)    NOT NULL UNIQUE                                     COMMENT '유저 이메일',
        password      VARCHAR(100)   NULL                                               COMMENT '유저 비밀번호',
        provider      VARCHAR(50)    NULL                                               COMMENT 'OAuth 제공자',
        provider_id   VARCHAR(100)   NULL                                               COMMENT 'OAuth 제공자 id',
        created_at    TIMESTAMP(6)   NOT NULL DEFAULT CURRENT_TIMESTAMP(6)              COMMENT '생성 시간',
        updated_at    TIMESTAMP(6)   NOT NULL DEFAULT CURRENT_TIMESTAMP(6) 
                                    ON UPDATE CURRENT_TIMESTAMP(6)                      COMMENT '수정 시간'
      ) ENGINE=InnoDB;
    `);

    await queryRunner.query(`ALTER TABLE user COMMENT = '유저의 중요 정보를 관리하는 테이블';`);
  }

  public async down(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(`DROP TABLE IF EXISTS user;`);
  }
}

생성된 마이그레이션 파일에 위처럼 SQL문을 작성해주면 된다.

up() 메서드를 통해 테이블과 필요한 주석을 생성하고, down() 메서드를 통해 생성된 테이블을 삭제한다.

마이그레이션 실행 및 롤백

// 실행 명령어
npm typeorm:prod migration:run

// 롤백 명령어
npm typeorm:prod migration:revert

마이그레이션 실행 명령어를 입력하면,

migrations 테이블이 생성되고,

TypeORM이 현재 데이터베이스 상태와 마이그레이션 파일을 비교하여, 아직 실행되지 않은 마이그레이션을 순서대로 실행한다. 이를 통해 데이터베이스 스키마를 최신 사앹로 업데이트한다.

profile
초보 개발자의 블로그입니다

0개의 댓글

관련 채용 정보