159
126

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-12-01

はじめに

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

導入

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

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

パッケージ追加

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

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

設定

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


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

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

ディレクトリ作成

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

  • src/entities
  • src/db/migrations
  • src/db/subscribers

TypeORMの設定

PJ直下に ormconfig.json を追加。

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等の環境変数を使ったり、条件による分岐ができるので嬉しいですね。

踏み込んだ設定が必要になった場合、各設定の書き方は これ 、設定出来る項目はこれを参考にしてください。

migration作成

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

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

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

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

では早速。

モデル定義作成

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

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 引数でマイグレーションに名前をつけます。アッパーキャメルケースで書きましょう。

# 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当てたほうがいいです)

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で作成されるテーブル名をカスタマイズする

migration適用

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

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

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

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

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

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

expressサーバーへの組み込み

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

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のまま使っちゃうほうが
混乱が少なくていいかと思います。

ormconfig.json
{
   // 注意: 本番環境にトランスパイル後のjsファイルを使用する場合、パス指定は工夫が必要
   // (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/

159
126
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
159
126

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?