Typescript + TypeORMセットアップ & migration世代管理 & expressへの組み込み

# はじめに

TypeScriptでバックエンドを書くなら、ORMはTypeORMがおすすめです。
が、日本語で導入からmigrationの世代管理まで説明している記事がなかったので起こしました。

# 導入

`npm init` して `express.js` と `typescript` 環境だけ突っ込んだPJだと仮定します。
馴染みのない人は[TypeScriptでExpress.js開発するときにやることまとめ (docker/lint/format/tsのまま実行/autoreload)](https://qiita.com/yuukive/items/012bdf1b9ff3881546b3)も合わせてどうぞ。

postgresqlとの組み合わせ例ですが、mysqlでも殆ど変わりません。


## パッケージ追加

まずは必要なライブラリのインストール。

```bash
npm install typeorm
npm install pg # MySQLの場合はmysql
npm install reflect-metadata
npm install @types/pg --save-dev
```

## 設定

TypeORMはDecorator機能を使用するため、 `tsconfig.json` に設定を追加

```js

{
    "compilerOptions": {
        ...
        "emitDecoratorMetadata": true,
        "experimentalDecorators": true
    },
    ...
}
```

また、 `"target": "es5"` だとエラーが起こるので、es6以上を指定します。

## ディレクトリ作成

ormconfigで使用するディレクトリを作成しておきます。
ActiveRecord慣れしている人はentitiesはmodelsという名前にするのもいいですね。

 - src/entities
 - src/db/migrations
 - src/db/subscribers


## TypeORMの設定

PJ直下に `ormconfig.json` を追加。

```js:ormconfig.json
{
   "type": "postgres",
   "host": "db", // 接続するDBホスト名
   "port": 5432,
   "username": "foo",  // DBユーザ名
   "password": "bar",  // DBパスワード
   "database": "test_db", // DB名
   // 注意" これがtrueだと、モデル定義を変更すると即DB反映されます。
   // 個人PJならいいですが、普通はmigrationファイルで世代管理すると思うのでfalseにします。
   "synchronize": false,
   "logging": false,
   "entities": ["src/entities/**/*.ts"],
   "migrations": ["src/db/migrations/**/*.ts"],
   "subscribers": ["src/db/subscribers/**/*.ts"],
   "cli": {
      "entitiesDir": "src/entities",
      "migrationsDir": "src/db/migrations",
      "subscribersDir": "src/db/subscribers"
   }
}
```

JSON以外にも、「jsファイル」「環境変数」「ymlファイル」「xmlファイル」が使え、設定方法はかなり柔軟です。
特にjsファイルで書くと`os.env.NODE_ENV`等の環境変数を使ったり、条件による分岐ができるので嬉しいですね。


踏み込んだ設定が必要になった場合、各設定の書き方は [これ](https://github.com/typeorm/typeorm/blob/master/docs/using-ormconfig.md#using-ormconfigjs) 、設定出来る項目は[これ](https://github.com/typeorm/typeorm/blob/master/docs/connection-options.md#what-is-connectionoptions)を参考にしてください。

# migration作成

セットアップ自体はここまでで終わりです。
ここからは実際にモデルとテーブルの作成を世代管理しながら回していく方法の紹介です。
ActiveRecord方式の説明をしますが、Repositoryパターンでも継承するクラスが
BaseEntityではなくBaseであること以外はほとんど同じです。

(事前準備)
postgresqlで接続しようとしているdbを作成しておきます。ここでは仮にtest_dbとします。
`create database test_db`


Railsではmigrationファイル作成→モデルが自動的に出来上がるという順番だと思いますが、
TypeORMはモデルを作成→migrationのgenerateコマンドで現在のDB状態とモデル定義を照らし合わせ、
migrationファイルを作成という順番になっています。
（既存のモデルも、定義を変更すればgenerateコマンドで差分を拾ってくれます）

個人的には、モデルを常に正義とするTypeORM型式のほうが好きです。


では早速。

## モデル定義作成

BaseEntityを継承してモデルクラスを作ります。

```ts:src/entities/User.ts
import { Entity, PrimaryGeneratedColumn, Column, BaseEntity } from 'typeorm'

@Entity()
export class User extends BaseEntity {
  @PrimaryGeneratedColumn()
  public id: number

  @Column()
  public name: string = ''

  @Column()
  public age: number = 0
}

export default User

```

## migrationファイル作成

TypeORMのcliで、migrationファイルを作成します。
「現在のDBスキーマ」と「modelsフォルダ内のモデル定義」を比較し、差分を作って自動的にmigrationを作成してくれます。
`-n`　引数でマイグレーションに名前をつけます。アッパーキャメルケースで書きましょう。

```sh
# tsファイルを直実行　
ts-node $(npm bin)/typeorm migration:generate -n Initialize
# ts-nodeがグローバルインストールされていない場合は`npm run ts-node`をpackage.jsonに書いた上でこう
npm run ts-node $(npm bin)/typeorm migration:generate -- -n Initialize

# jsコンパイルしたファイルを実行するならこう。この方式で行く場合はormconfigの各種パスがトランスパイル後のものになっている必要がある。
$(npm bin)/typeorm migration:generate -n Initialize

```

そうすると、ormconfigで指定したディレクトリ（記事ではsrc/db/migrations）に、タイムスタンプ+マイグレーション名のファイルが出来上がります。
ちなみに、migration:generateではなくmigration:createコマンドを使用すれば空のファイルが作成できます。
(PJでLinter/Formatterを使っている場合は、自動生成されるコードがそのルール通りではない
 可能性が高いので、ちゃんとLinter/Formatter当てたほうがいいです)

```ts:src/db/migrations/Initialize1540352770065.ts
import {MigrationInterface, QueryRunner} from "typeorm";

export class Initialize1543643478560 implements MigrationInterface {

    public async up(queryRunner: QueryRunner): Promise<any> {
        await queryRunner.query(`CREATE TABLE "user" ("id" SERIAL NOT NULL, "name" character varying NOT NULL, "age" integer NOT NULL, CONSTRAINT "PK_cace4a159ff9f2512dd42373760" PRIMARY KEY ("id"))`);
    }

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

(追記)
デフォルトでTypeORMが吐き出すテーブル名やカラム名、リレーションの**名前規約があまりイケていません**。
とりあえず試してみるという人はそのままでOKですが、ガッツリTypeORM使う人向けに
それらを修正する記事も書きました。
[TypeORMのmigrationで作成されるテーブル名をカスタマイズする](https://qiita.com/yuukive/items/cd8b7c5a973771d46489)

# migration適用

先程作成したmigrationを実際にDBに適用します。

```sh
# tsファイルを直実行。作成したmigrationの`up`関数が実行される。
ts-node $(npm bin)/typeorm migration:run

# jsコンパイルしたファイルを実行するならこう。
$(npm bin)/typeorm migration:run

```

また、TypeORMは世代管理のためにDBにmigrationsテーブルを作成します。
このテーブルを見るとどの世代までのmigrationが当たっているのかを確認できます。
（テーブル名はormconfigファイルで変更できます）


もしも世代を下げる必要があれば、以下コマンドで可能です。

```sh
# 作成したmigrationの`down`関数が実行される。
ts-node $(npm bin)/typeorm migration:revert
```



# expressサーバーへの組み込み

expresss自体に何かをする必要はありません。
サーバ立ち上げ前に、TypeORMにDB接続情報を渡してあげるだけです。

```ts:src/index.ts

import * as express from 'express'
import { User } from './entities/User'
import { getConnectionOptions, createConnection, BaseEntity } from 'typeorm'

let app = async () => {
  const app = express()

  // --- TypeORMの設定
  const connectionOptions = await getConnectionOptions()
  const connection = await createConnection(connectionOptions)
  // ActiveRecordパターンでTypeORMを使用する場合
  BaseEntity.useConnection(connection)

  app.get('/', async (req, res) => {
    const user = new User()
    user.name = 'Qiita'
    user.age = 25
    await user.save()
    const users = await User.find()
    res.send(users)
  })
  app.listen(3000, () => console.log('Example app listening on port 3000!'))
}

app()

```

上記を `ts-node src/index.ts` で立ち上げれば、 localhost:3000/にアクセスするたびにユーザーが増えて
ユーザーテーブルにあるユーザー一覧が返るはずです。

（追記）
User.find()は引数なしでDBレコード全部返ります。引数にオプションオブジェクトを渡すことで色々できます。
例: `find({ where: { name: 'Qiita'} })` レコードの引き方は豊富なオプションが有るので以下参照。
Repositoryパターンの例しか出てきませんが、ARパターンでも似たような感覚でオプション指定できます。
http://typeorm.io/#/find-options


# その後

あとは普段のexpress．js開発のとおりです。

# 本番環境について

jsにトランスパイルしてnodeコマンドで実行する場合は各種パスの指定を
トランスパイル後のものにする必要があります。
(よって、トランスパイルは元のディレクトリ構造をキープするように吐き出したほうが幸せになれます)


**追記:**
ts-nodeの本番環境運用も試しており、結論から言うと起動時間が若干長いという以外の
オーバーヘッドは殆ど無いので、TypeORMを使う場合はts-nodeのまま使っちゃうほうが
混乱が少なくていいかと思います。

```js:ormconfig.json
{
   // 注意: 本番環境にトランスパイル後のｊｓファイルを使用する場合、パス指定は工夫が必要
   // (entities, migrations, subscribersはトランスパイル後のパスを指定する)
   "entities": ["src/entities/**/*.ts"],
   "migrations": ["src/db/migrations/**/*.ts"],
   "subscribers": ["src/db/subscribers/**/*.ts"],
   "cli": {
      "entitiesDir": "src/entities",
      "migrationsDir": "src/db/migrations",
      "subscribersDir": "src/db/subscribers"
   }
}
```

# Ref

英語であれば、公式リファレンスがそこそこ充実しています。
http://typeorm.io/