Typeorm でマイグレーションをしよう

## はじめに

Typescript 用の OR マッパー Typeorm を使用して、環境設定からマイグレーションをする方法まで紹介します。

[公式のリファレンスはこちら](https://typeorm.io/#/)

## 前提

```bash
$ node -v
v14.9.0

$ yarn -v
1.22.4

$ mysql --version
mysql  Ver 8.0.19 for Linux on x86_64 (MySQL Community Server - GPL)
```

## 環境構築

まず、Typescript 環境を構築するところから始めます。この記事ではパッケージマネージャに yarn 使用しますが、NPM や他のものをお使いの方は、適宜読み替えてください。

```bash
yarn init -y
```

これで package.json が作成されました。続いて、Typescript 関連のツールと Typeorm をインストールします。その他 Linter や Formatter などはお好きな物をお使いください。

```bash
yarn add typeorm
yarn add -D typescript ts-node
```

また、今回は DB として MySQL を利用するので、それ用のドライバも合わせてインストールします。

```
yarn add mysql
```

MySQL 以外の DB を使う場合は、以下を参考に適宜ドライバをインストールしてください。

```bash
# PostgreSQL
yarn add pg

# SQLite
yarn add sqlite3

# Microsoft SQL Server
yarn add sql.js

# Oracle
yarn add oracledb

# MongoDB
yarn add mongodb

# Aurora Data API
yarn add typeorm-aurora-data-api-driver
```

さて、ドライバもインストールできたところで、TypeORM のプロジェクトを作成します。
手動でやってもいいのですが、TypeORM の CLI にはテンプレート作成機能があるので、それを利用します。

```bash
yarn typeorm init
```

コマンドを実行すると以下のようなファイルが生成されたのが確認できます。

```bash
~
├── src
│   ├── entity
│   │   └── User.ts  // サンプルエンティティ
│   ├── migration    // マイグレーションファイルを置くところ
│   └── index.ts
├── .gitignore
├── ormconfig.json   // TypeORMの設定情報
├── package.json
├── README.md
└── tsconfig.json
```

ormconfig.json というファイルは、TypeORM 用の設定ファイルです。
軽く設定項目について見てみましょう。

```js[ormconfig.json]
{
   "type": "mysql",
   "host": "localhost",
   "port": 3306,
   "username": "test",
   "password": "test",
   "database": "test",
   "synchronize": false, // コネクションを生成時にエンティティを自動でマイグレーションするかどうか。
   "logging": false, // Trueにすると発行したSQLのログが見れます。パフォーマンスが若干悪くなるため、開発環境での利用を推奨します。
   "entities": [
      "src/entity/**/*.ts"
   ],
   "migrations": [
      "src/migration/**/*.ts"
   ],
   "subscribers": [
      "src/subscriber/**/*.ts"
   ],
   "cli": {
      "entitiesDir": "src/entity",
      "migrationsDir": "src/migration",
      "subscribersDir": "src/subscriber"
   }
}
```

type や host、port などはお使いの DB に合わせて変更してください。`synchronize`については、false に設定してください。true にするとコネクション生成時に自動的にマイグレーションが実行されます。この挙動は開発時には便利ですが、プロダクション環境では推奨されない使い方です。今回は手動でマイグレーションをするため false に設定します。

ちなみに ormconfig は json 形式以外にも、以下の形式や環境変数からの読み込みもサポートしています。[詳細はこちらを確認してください。](https://github.com/typeorm/typeorm/blob/master/docs/using-ormconfig.md#creating-a-new-connection-from-the-configuration-file])

- .js
- .yml
- .xml

環境構築は以上となります。次は実際にエンティティを触ってマイグレーションをしていきます。

## エンティティを定義してテーブルを作ろう

TypeORM のテンプレートを利用したでは既に`User.ts`というファイルが生成されているかと思います。こちらはサンプルのエンティティですが、今回はこのエンティティを使って説明をしていきます。

```js[src/User.ts]
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  firstName: string;

  @Column()
  lastName: string;

  @Column()
  age: number;
}
```

ormconfig の`synchronize`を false にした場合は、エンティティの追加やカラムの追加など、DB のスキーマの変更に関する変更を行う場合は、マイグレーションをする必要があります。

マイグレーションの流れとしては、

0. マイグレーションファイルを生成する。
1. マイグレーションを実行する。

という流れになります。他の ORM と同様シンプルですね。

### 1.マイグレーションファイルを生成する

マイグレーションファイルの生成には主に３つの方法があります。

1. TypeORM CLI によるエンティティを参照した自動生成
2. TypeORM CLI によるマイグレーションファイルテンプレートの自動生成
3. スクラッチによるマイグレーションファイルの生成

今回は一番ラクな 1 を行います。

まずは、DB への接続ができるかの確認の意味を含めて、以下のコマンドを実行します。
ts-node の引数として Typeorm の実行ファイルを指定する必要があることに注意してください。

```bash
yarn ts-node node_modules/.bin/typeorm migration:show
```

このコマンド自体は、マイグレーションの履歴を出力するコマンドですが、実際に DB に接続して情報を取得するため、接続確認用として使用できます。

ここで MySQL の場合、`ER_NOT_SUPPORTED_AUTH_MODE`のようなエラーを受け取ることがあります。
これは、mysql ドライバが古いことが起因しているようです。
MySQL Client から DB に対して、古い認証方式も受け入れるように設定しましょう。

```bash
mysql> ALTER USER 'root' IDENTIFIED WITH mysql_native_password '<your-password>';
mysql> FLUSH PRIVILEGES;
```

その後、もう一度 TypeORM のコマンドを実行すると、次のような SQL のログを出すことが確認できます。それでもエラーが出る場合は、ormconfig の port や password を見直してみてください。

```bash
query: SELECT * FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = 'test' AND `TABLE_NAME` = 'migrations'
query: SELECT * FROM `test`.`migrations` `migrations` ORDER BY `id` DESC
```

今は特にマイグレーションファイルなどを生成してないため、結果は何も出力されませんが、DB との接続が確認できました。
では、実際にマイグレーションファイルを生成しましょう。

以下のコマンドを実行します。

```bash
yarn ts-node node_modules/.bin/typeorm migration:generate -n Test
```

-n は**必須**のパラメータでファイル名を指定します。コマンドの結果として migration というディレクトリに`タイムスタンプ + -n で指定した名前(ここでは Test) + .ts` というファイルが生成されています。

```js[~/src/migration/timestamp-Test.ts]
import {MigrationInterface, QueryRunner} from "typeorm";

export class Test1599199531504 implements MigrationInterface {
    name = 'Test1599199531504'

    public async up(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query("CREATE TABLE `user` (`id` int NOT NULL AUTO_INCREMENT, `firstName` varchar(255) NOT NULL, `lastName` varchar(255) NOT NULL, `age` int NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB");
    }

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

}
```

このファイルを簡単に説明すると、class の up というメソッドは、実際にマイグレーションの際に実行されるクエリです。queryRunner の query というメソッドには、生の SQL を書くことができます。また、down には up の対のクエリとして、 マイグレーションのロールバックの際に利用されるクエリが生成されます。

このマイグレーションファイルですが、もちろん手動で書き換えることができます。また、queryRunnser には query メソッド以外にも、addColumn や createIndex といったメソッドを持っており、生の SQL だけでなく、構造化されたデータを引数にとってクエリを生成することもできます。

### ２.マイグレーションを実行する

マイグレーションファイルが生成されたので、あとは実行するだけです。
実行は以下のコマンドです。

```bash
$ yarn ts-node node_modules/.bin/typeorm migration:run

---
一部抜粋
0 migrations are already loaded in the database.
1 migrations were found in the source code.
1 migrations are new migrations that needs to be executed.
```

これでマイグレーションが実行され、新しいテーブルが作成されました。
一応、現在のマイグレーションの状況も確認しておきましょう。

```bash
$ yarn ts-node node_modules/.bin/typeorm migration:show

---
一部抜粋
 [X] Test1599199531504
```

すると、チェックマークとともに、現在適応されたマイグレーションの一覧を取得できます。ちなみに、マイグレーションの実行履歴は、DB の migrations というテーブルが自動的に生成され、そこに記録されていきます。

また、マイグレーションファイルが複数ある場合にマイグレーションを実行すると、タイムスタンプ順に未適応のマイグレーションが全て実行されます。

一方、マイグレーションをロールバックのしたい場合は、

```bash
yarn ts-node node_modules/.bin/typeorm migration:revert
```

によって、直近のマイグレーションを１つがロールバックされます。複数個ロールバックしたい場合は複数回このコマンドを叩く必要があります。

以上がマイグレーションの一連の流れになります。

今回は何もない状態から、テーブルの作成を行うという例でしたが、エンティティを変更した場合などは同じようにマイグレーションファイルの生成 → 実行という手順でマイグレーションを行うことができます。それでは。

*本記事は @qualitia_cdevの中の一人、宮内さんに書いていただきました。