この記事は、2021/10/10時点のTypeORMの仕様に依存します。
TypeORMのバージョンが違ったり、記事の公開から長い時間が経っている場合、正確ではない可能性があります。
typeorm: v0.2.38
翻訳元: migrations.md
ライセンス: The MIT License
目次
マイグレーションとは
本番環境でもモデルの変更をデータベースに同期する必要があるわけですが、本番環境のデータベースにデータを入れた状態で、synchronize: true
を使うのは危険です。そこで、マイグレーションを使います。
マイグレーションの正体は普通のJSファイルで、SQLを使用してデータベースのスキーマを更新したり、既存のデータベースに変更を加えるというモノです。
では、すでにデータベースと、Postエンティティがあると仮定します。
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@Column()
text: string;
}
このエンティティは、数ヶ月の間、本番環境で特に何も変更を加えられていませんでした。そして、その間に数千件の投稿がデータベースに保存されていました。
この状態で、title
というカラム名をname
に変更する必要が出てきました。どうすれば良いでしょうか。
正解は、以下のSQLを実行するマイグレーションを新規作成すれば良いです。(PostgreSQLの例)
ALTER TABLE "post" ALTER COLUMN "title" RENAME TO "name";
このSQLを実行すれば、データベースが更新されます。TypeORMはこのようなSQLを記述する 場所 を提供し、必要なときに実行できるようにします。この"場所"のことをマイグレーションと呼んでいます。
マイグレーションを作成する
事前にCLIのインストールが必要です。
マイグレーションを作成する前に、セットアップが必要です。
{
"type": "mysql",
"host": "localhost",
"port": 3306,
"username": "test",
"password": "test",
"database": "test",
"entities": ["entity/*.js"],
"migrationsTableName": "custom_migration_table",
"migrations": ["migration/*.js"],
"cli": {
"migrationsDir": "migration"
}
}
オプションの詳細は以下の通りです。
-
"migrationsTableName": "migrations"
- マイグレーションテーブルの名前をオプションの
migrations
以外にする場合に指定します。
- マイグレーションテーブルの名前をオプションの
-
"migrations": ["migration/*.js"]
- マイグレーションの読み込み先のディレクトリを指定します。
-
"cli": { "migrationsDir": "migration" }
- CLIを利用して作成するマイグレーションの保存先ディレクトリを指定します。
セットアップが完了したら、CLIでマイグレーションを作成することができます。
typeorm migration:create -n PostRefactoring
PostRefactoring
というのがマイグレーションの名前を指していて、ここには好きな名前を指定することができます。このコマンドを実行すると、"migration"ディレクトリに{TIMESTAMP}-PostRefactoring.ts
という形式のファイルが作成されます。{TIMESTAMP}
の部分は、マイグレーションを作成した日時のタイムスタンプです。では、ファイルを開いて、SQLを書き込んでみましょう。
マイグレーションの中身は、以下のようになっているはずです。
import {MigrationInterface, QueryRunner} from "typeorm";
export class PostRefactoringTIMESTAMP implements MigrationInterface {
async up(queryRunner: QueryRunner): Promise<void> {
}
async down(queryRunner: QueryRunner): Promise<void> {
}
}
up
とdown
という二つのメソッドがあります。up
はマイグレーションそのものを書くためのもので、down
はup
で行った変更を取り消すためのものです。
up
もdown
も、QueryRunner
オブジェクトを持っています。データベースに対する操作は、全てこのオブジェクトを介して行います。Query Runnerについてはこちらを参照してください。
Post
を変更するマイグレーションは以下のようになります。
import {MigrationInterface, QueryRunner} from "typeorm";
export class PostRefactoringTIMESTAMP implements MigrationInterface {
async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "post" RENAME COLUMN "title" TO "name"`);
}
async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "post" RENAME COLUMN "name" TO "title"`); // reverts things made in "up" method
}
}
マイグレーションを実行する
マイグレーションを作成したら、CLIコマンドで実行することができます。
typeorm migration:run
typeorm migration:create
かtypeorm migration:generate
コマンドをo
フラグを付けずに実行した場合、.ts
ファイルが生成されます。(詳しくは、Generating migrationsをご参照ください。)migration:run
とmigration:revert
コマンドでは、.js
ファイルしか扱うことができません。従って、TypeScriptファイルは、マイグレーションの実行前にトランスパイルされている必要があります。他の方法としては、typeorm
をts-node
と組み合わせて使用することで、.ts
拡張子のマイグレーションファイルを実行することができます。
-
ts-node
のサンプル
ts-node --transpile-only ./node_modules/typeorm/cli.js migration:run
-
ts-node
のサンプル(node_modules
を参照しない場合)
ts-node $(yarn bin typeorm) migration:run
これらのコマンドは、保留中の全てのマイグレーションをタイムスタンプ順で実行します。言い換えると、作成したマイグレーションのup
メソッド内に書いたSQLクエリが全て実行されるということです。これで、データベーススキーマを更新することができました。
マイグレーションを取り消すときは、以下のコマンドを実行します。
typeorm migration:revert
このコマンドは、実行した中で最新のマイグレーションのdown
を実行します。複数のマイグレーションを取り消す必要がある時は、このコマンドを複数回呼び出してください。
マイグレーションを自動生成する
TypeORMでは、スキーマに対して行った変更から、自動的にマイグレーションを生成する機能があります。
title
カラムを持つPost
エンティティがあったとして、title
をname
に変更した場合を想定しましょう。以下のコマンドを実行したとします。
typeorm migration:generate -n PostRefactoring
すると、以下の内容を含む{TIMESTAMP}-PostRefactoring.ts
の形式のファイルが自動生成されます。
import {MigrationInterface, QueryRunner} from "typeorm";
export class PostRefactoringTIMESTAMP implements MigrationInterface {
async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "post" ALTER COLUMN "title" RENAME TO "name"`);
}
async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "post" ALTER COLUMN "name" RENAME TO "title"`);
}
}
o
フラグ(--outputJs
のエイリアス)を使って、JavaScriptファイルを生成することもできます。TypeScriptのパッケージがインストールされていなくて、JavaScriptのみのプロジェクトの場合は便利です。その場合は、以下の内容を含んだ{TIMESTAMP}-PostRefactoring.js
ファイルが生成されます。
const { MigrationInterface, QueryRunner } = require("typeorm");
module.exports = class PostRefactoringTIMESTAMP {
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "post" ALTER COLUMN "title" RENAME TO "name"`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "post" ALTER COLUMN "title" RENAME TO "name"`);
}
}
自分でSQLクエリを書く必要がないというのは便利ですね。ただし、この方法では、モデルに変更を加えるたびにマイグレーションを生成してください。複数行のSQLクエリを含むマイグレーションを生成するには、p
フラグ(--pretty
のエイリアス)を使用してください。
コネクションオプション
デフォルトではないコネクションに対してrun
やrevert
を実行する必要がある場合は、-c
フラグ(--connection
のエイリアス)を使用して、コンフィグ名を引数として渡すことができます。
ここの説明はちょっと分かりにくいので、こっちを参照→Using ormconfig.json
マイグレーションを記述するときに利用できるAPI
APIを使ってデータベーススキーマを変更するときには、QueryRunner
を使用します。
サンプル
import {MigrationInterface, QueryRunner, Table, TableIndex, TableColumn, TableForeignKey } from "typeorm";
export class QuestionRefactoringTIMESTAMP implements MigrationInterface {
async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.createTable(new Table({
name: "question",
columns: [
{
name: "id",
type: "int",
isPrimary: true
},
{
name: "name",
type: "varchar",
}
]
}), true)
await queryRunner.createIndex("question", new TableIndex({
name: "IDX_QUESTION_NAME",
columnNames: ["name"]
}));
await queryRunner.createTable(new Table({
name: "answer",
columns: [
{
name: "id",
type: "int",
isPrimary: true
},
{
name: "name",
type: "varchar",
},
{
name: 'created_at',
type: 'timestamp',
default: 'now()'
}
]
}), true);
await queryRunner.addColumn("answer", new TableColumn({
name: "questionId",
type: "int"
}));
await queryRunner.createForeignKey("answer", new TableForeignKey({
columnNames: ["questionId"],
referencedColumnNames: ["id"],
referencedTableName: "question",
onDelete: "CASCADE"
}));
}
async down(queryRunner: QueryRunner): Promise<void> {
const table = await queryRunner.getTable("answer");
const foreignKey = table.foreignKeys.find(fk => fk.columnNames.indexOf("questionId") !== -1);
await queryRunner.dropForeignKey("answer", foreignKey);
await queryRunner.dropColumn("answer", "questionId");
await queryRunner.dropTable("answer");
await queryRunner.dropIndex("question", "IDX_QUESTION_NAME");
await queryRunner.dropTable("question");
}
}
マイグレーションを記述するときに利用できるAPI
getDatabases(): Promise<string[]>
全てのデータベース名を取得。
getSchemas(database?: string): Promise<string[]>
-
database
- データベースが指定されると、そのデータベースのスキーマを返す
全てのスキーマ名を取得。SQLServerとPostgreSQLでのみ使用可能。
getTable(tableName: string): Promise<Table|undefined>
-
tableName
- 読み込むテーブル名
指定した名前のテーブルを読み込む。
getTables(tableNames: string[]): Promise<Table[]>
-
tableNames
- 読み込むテーブル名(複数)
指定した名前のテーブルを読み込む(複数)。
hasDatabase(database: string): Promise<boolean>
-
database
- チェックするデータベース名
指定した名前のデータベースが存在するかをチェックする。
hasSchema(schema: string): Promise<boolean>
-
schema
- チェックするスキーマ名
指定した名前のスキーマが存在するかチェックする。SQLServerとPostgreSQLでのみ使用可能。
hasTable(table: Table|string): Promise<boolean>
-
table
- テーブルのオブジェクト、もしくは名前
テーブルが存在しているかチェックする。
hasColumn(table: Table|string, columnName: string): Promise<boolean>
-
table
- テーブルのオブジェクト、もしくは名前 -
columnName
- チェックするカラムの名前
テーブルに指定したカラムが存在するかチェックする。
createDatabase(database: string, ifNotExist?: boolean): Promise<void>
-
database
- データベース名 -
ifNotExist
- すでにデータベースが存在したとき、true
が指定されていればスキップするが、そうでなければ例外が投げられる
新しいデータベースを作成する。
dropDatabase(database: string, ifExist?: boolean): Promise<void>
-
database
- データベース名 -
ifExist
- データベースが存在しなかったとき、true
が指定されていればスキップするが、そうでなければ例外が投げられる
データベースを削除する。
createSchema(schemaPath: string, ifNotExist?: boolean): Promise<void>
-
schemaPath
- スキーマ名。SQLServerではスキーマパスをパラメータとして渡せる。スキーマパスを渡した場合、指定されたデータベースにスキーマが作成される。 -
ifNotExist
- すでにスキーマが存在したとき、true
が指定されていればスキップするが、そうでなければ例外が投げられる
新しいテーブルスキーマを作成する。
dropSchema(schemaPath: string, ifExist?: boolean, isCascade?: boolean): Promise<void>
-
schemaPath
- スキーマ名。SQLServerではスキーマパスをパラメータとして渡せる。スキーマパスを渡した場合、指定されたデータベースにスキーマが削除される。 -
ifExist
- データベースが存在しなかったとき、true
が指定されていればスキップするが、そうでなければ例外が投げられる -
isCascade
-true
が指定された場合、スキーマに含まれるオブジェクト(テーブルや関数)も削除される。PostgreSQLでのみ指定可能。
テーブルスキーマを削除する。
createTable(table: Table, ifNotExist?: boolean, createForeignKeys?: boolean, createIndices?: boolean): Promise<void>
-
table
- テーブルオブジェクト。 -
ifNotExist
- すでにスキーマが存在したとき、true
が指定されていればスキップするが、そうでなければ例外が投げられる -
createForeignKeys
- テーブル作成時に外部キーを作成するかどうか(デフォルトはtrue
) -
createIndices
- テーブル作成時にインデックスを作成するかどうか(デフォルトはtrue
)
テーブルを作成する。
以降はAPIの詳細が続くので、原文を参照してください。