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


はじめに

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

公式のリファレンスはこちら

前提

$ 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 や他のものをお使いの方は、適宜読み替えてください。

yarn init -y

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

yarn add typeorm
yarn add -D typescript ts-node

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

yarn add mysql

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

# 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 にはテンプレート作成機能があるので、それを利用します。

yarn typeorm init

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

~
├── 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 形式以外にも、以下の形式や環境変数からの読み込みもサポートしています。詳細はこちらを確認してください。

  • .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 のスキーマの変更に関する変更を行う場合は、マイグレーションをする必要があります。

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

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

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

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

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

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

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

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

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

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

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

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

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

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 との接続が確認できました。
では、実際にマイグレーションファイルを生成しましょう。

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

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 だけでなく、構造化されたデータを引数にとってクエリを生成することもできます。

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

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

$ 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.

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

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

---
一部抜粋
 [X] Test1599199531504

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

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

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

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

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

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

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

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