Client에 배포를 할 때, 클라이언트 측 DB에 Admin계정이 존재하는지 확인하고, 만약 없다면 해당 DB에 정해진 계정을 넣어줘야 하는 일이 생겼다. 어떻게 이러한 문제를 TypeORM의 migration으로 해결 할 수 있는지 정리해보자.
개발 환경
우선 현재 NodeJS기반에서 NestJS 프레임워크, DB는 Mysql, TypeORM을 사용하고 있으며, Docker를 이용하고 있다.
DB는 docker로 띄우지 않고, 폐쇄망에 따로 마련된 DB를 이용하고 있다. (참고로, Front, Backend 두개의 컨테이너를 띄우고 있다.)
Migration?
우선 migration이 무엇인지에 대해 간략히 정리하고 넘어가자.
NestJS의 공식문서에 따르면, Migrations은 기존 데이터베이스에 존재하던 데이터들을 보존하면서, database의 스키마를 업데이트 하는 것을 의미한다.
NestJS와 TypeORM에서는 synchronize: true 라는 옵션을 통해, database 스키마의 sync를 맞출 수 있지만,
production 단계라면, 이미 데이터베이스에 data들이 들어가 있을 것이고, 위의 옵션은 해당 데이터들이 날아 가버릴 수 있는 위험한 옵션이기에, 개발단계에서만 사용해야 한다. 그리고 이때 migrations의 필요성이 대두되는 것이다.
How to?
실제로 어떻게 migration 파일을 만들고 run 할 수 있는지 step by step 알아보자.
1. Pre-requisites
TypeOrm이 먼저 설치가 되어 있어야 한다.
아래 명령어로 cli-install 이 가능하다.
$ npm install --save @nestjs/typeorm typeorm@0.2 mysql2
2. 환경설정
ormconfig.ts 파일을 통해, 필요한 설정들을 해주자.
이때 파일 위치는 root에 위치한다.(src/ormconfig.ts)
import { ConnectionOptions } from 'typeorm';
// You can load you .env file here synchronously using dotenv package (not installed here),
import * as dotenv from 'dotenv';
import * as fs from 'fs';
const environment = process.env.NODE_ENV || 'local';
const data: any = dotenv.parse(fs.readFileSync(`.env.${environment}`));
// You can also make a singleton service that load and expose the .env file content.
// ...
// Check typeORM documentation for more information.
const config: ConnectionOptions = {
type: 'mysql',
host: data.DATABASE_HOST,
port: data.DATABASE_PORT,
username: data.DATABASE_USER,
password: data.DATABASE_PASSWORD,
database: data.DATABASE_NAME,
entities: [__dirname + '/**/*.entity{.ts,.js}'],
//IT SHOULD BE FALSE WHEN PROD OR DEVELOP
synchronize: false,
//If it is true, automatically migration occurs.
migrationsRun: false,
logging: true,
logger: 'file',
// Allow both start:prod and start:dev to use migrations
// __dirname is either dist or src folder, meaning either
// the compiled js in prod or the ts in dev.
migrations: [__dirname + '/migrations/**/*{.ts,.js}'],
cli: {
// Location of migration should be inside src folder
// to be compiled into dist/ folder.
migrationsDir: 'src/migrations',
},
};
export = config;
위 코드에서 주석으로 설명 했듯이,
1. synchronize: database와 스키마 싱크를 맞춘다. (단, data 유실의 위험이 있으므로, production에서는 false로 둔다.)
2. migrationsRun: 자동적으로 처음 migration이 실행되도록 한다.
3. cli:{migrationsDir: } : migration파일이 생성될 때 src/migrations에 생성 되어야 한다.
3. App.module.ts에 등록
import * as ormconfig from './ormconfig';
@Module({
imports: [
...
TypeOrmModule.forRoot(ormconfig),
...
]
})
위와 같은 방식으로 설정한 ormconfig를 등록한다.
4. package.json scripts 수정
"typeorm": "node --require ts-node/register ./node_modules/typeorm/cli.js --config src/ormconfig.ts",
"typeorm:migrate": "npm run typeorm migration:create -- -n",
package.json파일의 scripts를 위와 같이 수정해준다.
이때 주의할 점은 typeorm에서 --config옵션을 통해, ormconfig.ts파일의 위치를 알려줘야 한다는 것이다.
5. migration 파일 생성
자, 이제 모든 준비가 끝났다. migration 파일을 생성해보자.
npm run typeorm:migrate defaultUser
위 명령어를 통해서, defaultUser라는 migration 파일을 생성한다.
위 옵션에서 설정한대로, migrations 폴더 안에 우리가 생성한 migration 파일이 생성된 것을 확인 할 수 있다.
6. migration 파일 작성
이제 생성된 migration 파일의 내용을 작성해보자.
import { MigrationInterface, QueryRunner } from 'typeorm';
export class defaultUser1651127221643 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
}
public async down(queryRunner: QueryRunner): Promise<void> {
}
}
처음 create를 통해 만들어진 migration 파일은 위와 같은 형태를 띄게 된다.
참고로 up 메소드는 migration:run 하였을 때 실행되게 되고,
down메소드의 경우 migrration:revert 했을 때 실행되게 된다.
나의 목적은 sAdmin 계정이 있는지 없는지 체크하고, 만약 있다면 그대로 return 만약 sAdmin계정이 없다면 새롭게 추가해주는 것이었다.
따라서 아래 코드처럼 up 부분을 구성해 주었다.
public async up(queryRunner: QueryRunner): Promise<void> {
const userRepo = queryRunner.connection.getRepository(User);
const result = await queryRunner.query(
`SELECT * FROM mcUser WHERE role_id = 1`,
);
const password = await bcrypt.hash('sAdmin', 10);
if (result.length === 0) {
await userRepo.insert({
id: 'sAdmin',
name: 'sAdmin',
email: 'sAdmin@sADmin.com',
password,
current_hashed_refresh_token: null,
is_active: true,
is_reset_pwd: false,
role: 1,
});
console.log('SAdmin has been created!');
return;
}
console.log('SAdmin already exists!');
}
'NestJS' 카테고리의 다른 글
main.ts 에서 nestjs module 사용하기 (feat. nestjs 서버 시작 로그 남기기) (0) | 2022.05.12 |
---|---|
nestjs Logger Middleware 만들기(feat. log db저장) (0) | 2022.05.12 |
nestJS app 밖에서 nestJS application instance 사용하기 (0) | 2022.04.29 |
[NestJS] filter안에 dependency injection하기(i.e. inject service) (0) | 2022.04.15 |
NestJS의 기초 (0) | 2022.04.11 |