๐ŸŽ‰ berenickt ๋ธ”๋กœ๊ทธ์— ์˜จ ๊ฑธ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค. ๐ŸŽ‰
Back
NestJs
38-Migrations

1. Migration

  • Migration์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ์Šคํฌ๋ฆฝํŠธ๋กœ ์ž‘์„ฑํ•ด์„œ ๋ฐ˜์˜ํ•œ๋‹ค.
    • ํ†ต์ œ๋œ ์ƒํ™ฉ์—์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ ๋ฐ ๋ณต๊ตฌ๋ฅผ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • Migration์ด ํ•„์š”ํ•œ ์ด์œ : ์™œ sync ์˜ต์…˜์œผ๋ก  ๋ถ€์กฑํ•œ๊ฐ€?
    • Controlled Changes : ์›ํ•˜๋Š” ์ƒํ™ฉ์— ์›ํ•˜๋Š” ํ˜•ํƒœ๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์ž์œ ๋กญ๊ฒŒ ์‹คํ–‰ ํ•  ์ˆ˜ ์žˆ๋‹ค
    • Reversible : ์ง„ํ–‰ํ•œ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์‰ฝ๊ฒŒ ๋˜๋Œ๋ฆด ์ˆ˜ ์žˆ๋‹ค.
    • Versioning : ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์€ ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ์— ๋Œ€ํ•œ ํžˆ์Šคํ† ๋ฆฌ๋ฅผ ๋‹ด๊ณ  ์žˆ๋‹ค. ๋””๋ฒ„๊น…์— ๋งค์šฐ ์œ ์šฉํ•˜๋‹ค.
    • Consistency : ๋‹ค์–‘ํ•œ ํ™˜๊ฒฝ์—์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ๊ฐ€ ๊ฐ™๊ฒŒ ์œ ์ง€๋˜๋„๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค.
    • Complex Changes : ๋ณต์žกํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ๋ณ€ํ™”๋ฅผ ์ง์ ‘ ์ปจํŠธ๋กค ํ•  ์ˆ˜ ์žˆ๋‹ค.

2. Migration ํŒŒ์ผ

1
Migration1
2
Migration2 # <------------- Database1
3
Migration3 ## Migration ์ ์šฉ
4
Migration4 ## Migration ์ ์šฉ
5
Migration5 ## Migration ์ ์šฉ
6
Migration6 ## Migration ์ ์šฉ

3. Migration Configuration : ormconfig.json

1
{
2
"type": "postgres",
3
"host": "localhost",
4
"port": 5432,
5
"username": "test",
6
"password": "test",
7
"database": "test",
8
"entities": ["src/entities/**/*.ts"],
9
"migrations": ["src/migrations/**/*.ts"], // Migration ํŒŒ์ผ ๊ฒฝ๋กœ
10
"cli": {
11
"entitiesDir": "src/entity",
12
"migrationsDir": "src/migrations"
13
}
14
}

4. Migration ํ…Œ์ด๋ธ” ์ƒ์„ฑ

4.1 RAW SQL

1
import { MigrationInterface, QueryRunner } from 'typeorm'
2
3
export class CreateMovieAndDirectorTables1634567890123 implements MigrationInterface {
4
public async up(queryRunner: QueryRunner): Promise<void> {
5
await queryRunner.query(`
6
CREATE TABLE "director" (
7
"id" SERIAL NOT NULL,
8
"name" VARCHAR NOT NULL,
9
"dob" DATE,
10
"nationality" VARCHAR,
11
PRIMARY KEY ("id")
12
)
13
`)
14
await queryRunner.query(`
15
CREATE TABLE "movie" (
16
"id" SERIAL NOT NULL,
17
"title" VARCHAR NOT NULL,
18
"genre" VARCHAR,
19
"directorId" INTEGER,
20
PRIMARY KEY ("id"),
21
FOREIGN KEY ("directorId") REFERENCES "director" ("id") ON DELETE SET NULL
22
)
23
`)
24
}
25
26
public async down(queryRunner: QueryRunner): Promise<void> {
27
await queryRunner.query(`DROP TABLE "movie"`)
28
await queryRunner.query(`DROP TABLE "director"`)
29
}
30
}

4.2 Migration API

1
import { MigrationInterface, QueryRunner, Table, TableForeignKey } from 'typeorm'
2
3
export class CreateMovieAndDirectorTables1634567890123 implements MigrationInterface {
4
public async up(queryRunner: QueryRunner): Promise<void> {
5
/// ...์ƒ๋žต
6
await queryRunner.createTable(
7
new Table({
8
name: 'movie',
9
columns: [
10
{ name: 'id', type: 'int', isPrimary: true, isGenerated: true, generationStrategy: 'increment' },
11
{ name: 'title', type: 'varchar' },
12
{ name: 'genre', type: 'varchar', isNullable: true },
13
{ name: 'directorId', type: 'int', isNullable: true },
14
],
15
}),
16
true,
17
)
18
await queryRunner.createForeignKey(
19
'movie',
20
new TableForeignKey({
21
columnNames: ['directorId'],
22
referencedColumnNames: ['id'],
23
referencedTableName: 'director',
24
onDelete: 'SET NULL',
25
}),
26
)
27
}
28
29
public async down(queryRunner: QueryRunner): Promise<void> {
30
await queryRunner.dropTable('movie')
31
await queryRunner.dropTable('director')
32
}
33
}

5. Migration ์นผ๋Ÿผ ์ถ”๊ฐ€

5.1 RAW SQL

1
import { MigrationInterface, QueryRunner } from 'typeorm'
2
3
export class AddDirectorBioColumn1634567890123 implements MigrationInterface {
4
public async up(queryRunner: QueryRunner): Promise<void> {
5
await queryRunner.query(`
6
ALTER TABLE "director"
7
ADD "dateOfBirth" DATE
8
`)
9
}
10
11
public async down(queryRunner: QueryRunner): Promise<void> {
12
await queryRunner.query(`
13
ALTER TABLE "director"
14
DROP COLUMN "dateOfBirth"
15
`)
16
}
17
}

5.2 Migration API

1
import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm'
2
3
export class AddDateOfBirthColumn1634567890124 implements MigrationInterface {
4
public async up(queryRunner: QueryRunner): Promise<void> {
5
await queryRunner.addColumn(
6
'director',
7
new TableColumn({
8
name: 'dateOfBirth',
9
type: 'date',
10
isNullable: true,
11
}),
12
)
13
}
14
15
public async down(queryRunner: QueryRunner): Promise<void> {
16
await queryRunner.dropColumn('director', 'dateOfBirth')
17
}
18
}

6. Migration ์นผ๋Ÿผ ์ด๋ฆ„ ๋ณ€๊ฒฝ

6.1 RAW SQL

1
import { MigrationInterface, QueryRunner } from 'typeorm'
2
3
export class RenameNameToFullName1634567890125 implements MigrationInterface {
4
public async up(queryRunner: QueryRunner): Promise<void> {
5
await queryRunner.query(`
6
ALTER TABLE "director"
7
RENAME COLUMN "name" TO "fullName"
8
`)
9
}
10
11
public async down(queryRunner: QueryRunner): Promise<void> {
12
await queryRunner.query(`
13
ALTER TABLE "director"
14
RENAME COLUMN "fullName" TO "name"
15
`)
16
}
17
}

6.2 Migration API

1
import { MigrationInterface, QueryRunner } from 'typeorm'
2
3
export class RenameNameToFullNameInDirector1634567890125 implements MigrationInterface {
4
public async up(queryRunner: QueryRunner): Promise<void> {
5
await queryRunner.renameColumn('director', 'name', 'fullName')
6
}
7
8
public async down(queryRunner: QueryRunner): Promise<void> {
9
await queryRunner.renameColumn('director', 'fullName', 'name')
10
}
11
}

7. Migration ์นผ๋Ÿผ ํƒ€์ž… ๋ณ€๊ฒฝ

7.1 RAW SQL

1
import { MigrationInterface, QueryRunner } from 'typeorm'
2
3
export class ChangeEmailTypeInDirector1634567890126 implements MigrationInterface {
4
public async up(queryRunner: QueryRunner): Promise<void> {
5
await queryRunner.query(`
6
ALTER TABLE "director"
7
ALTER COLUMN "email" TYPE TEXT
8
`)
9
}
10
11
public async down(queryRunner: QueryRunner): Promise<void> {
12
await queryRunner.query(`
13
ALTER TABLE "director"
14
ALTER COLUMN "email" TYPE VARCHAR
15
`)
16
}
17
}

7.2 Migration API

1
import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm'
2
3
export class ChangeEmailTypeInDirector1634567890126 implements MigrationInterface {
4
public async up(queryRunner: QueryRunner): Promise<void> {
5
await queryRunner.changeColumn(
6
'director',
7
'email',
8
new TableColumn({
9
name: 'email',
10
type: 'text',
11
}),
12
)
13
}
14
15
public async down(queryRunner: QueryRunner): Promise<void> {
16
await queryRunner.changeColumn(
17
'director',
18
'email',
19
new TableColumn({
20
name: 'email',
21
type: 'varchar',
22
}),
23
)
24
}
25
}

8. Migration : Relationship ์ž‘์—…

8.1 RAW SQL

1
import { MigrationInterface, QueryRunner } from 'typeorm'
2
3
export class CreateGenreAndMovieGenreTables1634567890127 implements MigrationInterface {
4
public async up(queryRunner: QueryRunner): Promise<void> {
5
await queryRunner.query(`
6
CREATE TABLE "genre" (
7
"id" SERIAL NOT NULL,
8
"name" VARCHAR NOT NULL,
9
PRIMARY KEY ("id")
10
)
11
`)
12
await queryRunner.query(`
13
CREATE TABLE "movie_genre" (
14
"movieId" INTEGER NOT NULL,
15
"genreId" INTEGER NOT NULL,
16
PRIMARY KEY ("movieId", "genreId"),
17
FOREIGN KEY ("movieId") REFERENCES "movie" ("id") ON DELETE CASCADE,
18
FOREIGN KEY ("genreId") REFERENCES "genre" ("id") ON DELETE CASCADE
19
)
20
`)
21
}
22
23
public async down(queryRunner: QueryRunner): Promise<void> {
24
await queryRunner.query(`DROP TABLE "movie_genres_genre"`)
25
await queryRunner.query(`DROP TABLE "genre"`)
26
}
27
}

8.2 Migration API

1
import { MigrationInterface, QueryRunner, Table, TableForeignKey } from 'typeorm'
2
3
export class CreateGenreAndMovieGenreTables1634567890127 implements MigrationInterface {
4
public async up(queryRunner: QueryRunner): Promise<void> {
5
await queryRunner.createTable(
6
new Table({
7
name: 'genre',
8
columns: [
9
{ name: 'id', type: 'int', isPrimary: true, isGenerated: true, generationStrategy: 'increment' },
10
{ name: 'name', type: 'varchar' },
11
],
12
}),
13
true,
14
)
15
//... ์ƒ๋žต
16
}
17
18
public async down(queryRunner: QueryRunner): Promise<void> {
19
await queryRunner.dropTable('movie_genres_genre')
20
await queryRunner.dropTable('genre')
21
}
22
}

9. Migration CLI ์ปค๋งจ๋“œ

1
# Migration ํŒŒ์ผ ์ƒ์„ฑ
2
npx typeorm migration:generate -n <MigrationName>
3
4
# Migration ์‹คํ–‰
5
npx typeorm migration:run
6
7
# Migration ๋˜๋Œ๋ฆฌ๊ธฐ
8
npx typeorm migration:revert