MikroORMのはじめ方 - インストールからINSERTとSELECTまで

この投稿は、TypeScript用ORMであるMikro ORMの初歩的なチュートリアルです。データベースはPostgreSQLを用います。

## このチュートリアルで学べること

- Mikro ORMのインストール方法
- Mikro ORMの設定方法
- Mikro ORMでのエンティティの`INSERT`と`SELECT`のやり方

## PostgreSQLを起動する

Mikro ORMは様々なデータベースに対応していますが、今回はPostgreSQLを使いたいので、PostgreSQLサーバーを起動しておきます。

PostgreSQLサーバーを起動するには、Dockerを使うのが手軽なので、それを使います。

docker-compose.ymlを用意します:

```docker-compose.yml
version: '3.7'
services:
  db:
    image: postgres:11.3
    # Make postgres logs. More information about logging, see official documentation: https://www.postgresql.org/docs/11/runtime-config-logging.html
    command: postgres -c log_destination=stderr -c log_statement=all -c log_connections=on -c log_disconnections=on
    ports:
      - 5432:5432
    environment:
      POSTGRES_USER: test
      POSTGRES_PASSWORD: test
      POSTGRES_INITDB_ARGS: "--encoding=UTF-8"
    restart: always
    user: root
    logging:
      options:
        max-size: "10k"
        max-file: "5"
```

この設定では、Dockerホストからアクセスできるようにポート5432を開放します。PostgreSQLのユーザはtestにし、パスワードはtestにしてあります。`command`にわちゃわちゃ書いてあるのはクエリーログを見れるようにするためのものです。

Docker ComposeでPostgreSQLサーバーを起動します:

```
docker-compose up
```

起動したらDockerホスト側からの接続テストを行います。接続テストには`psql`コマンドを使います。このコマンドがMacOSに入っていない場合は`brew install postgresql`してください。

```
PGPASSWORD=test psql \
    --host 127.0.0.1 \
    --port 5432 \
    --username test \
    --dbname test
```

このコマンドでプロンプトが立ち上がれば、接続OKです。

## 必要なパッケージをインストールする

必要なパッケージをインストールします。まず、TypeScriptは必須なので入れます。TypeScriptをコンパイルなしに実行したいので、`ts-node`も入れます。

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

次にMikro ORMのコアと、今回はデータベースはPostgreSQLを使うので、そのコネクターをインストールします。TypeScriptで定義したエンティティの型情報はしっかり認識させたいので、`@mikro-orm/reflection`も入れておきます:

```
yarn add @mikro-orm/core @mikro-orm/postgresql @mikro-orm/reflection
```

Mikro ORMのCLIツールも入れます:

```
yarn add -D @mikro-orm/cli
```

Mikro ORMのCLIツールがインストールできているか確認します:

```
$ npx mikro-orm --version
4.4.4
```

## TypeScriptのコンパイルオプションを設定する

Mikro ORMはデコレーターを使うので、TypeScriptのコンパイルオプションでそれを有効にします。

まず、TypeScriptの設定ファイルを生成します:

```
npx tsc -init
```

次に、デコレーター周りのオプションを有効化します:

```jsonnet:tsconfig.json
{
  "compilerOptions": {
    /* ... */
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "esModuleInterop": true
  }
}
```

## Mikro ORMの設定ファイルを作る

データベースの接続情報などを書いておきます。

```mikro-orm.config.ts
import { Options } from "@mikro-orm/core";
import { PostgreSqlDriver } from "@mikro-orm/postgresql";
import { TsMorphMetadataProvider } from "@mikro-orm/reflection";

const options: Options<PostgreSqlDriver> = {
  driver: PostgreSqlDriver,
  metadataProvider: TsMorphMetadataProvider,
  entities: ["./entities/*.js"],
  entitiesTs: ["./entities/*.ts"],
  clientUrl: "postgresql://test:test@127.0.0.1:5432/test",
};

export default options;
```

このファイルはMikro ORM CLIでデータベースを扱うときに使います。アプリ側からも同じ設定を使っていい場合は、このファイルを`import`して流用できます。

`metadataProvider`はエンティティのメタデータをどの形式で書くかを決めるものです。Mikro ORMではいくつかの形式をサポートしていますが、TypeScriptの場合もっとも記述量が少なくて済む`TsMorphMetadataProvider`がオススメです。

`entitiesTs`では、どのファイルにエンティティがあるかを指定します。これをヒントにMikro ORMはエンティティを探し出します。

`clientUrl`はデータベースの接続先URLです。設定ファイルでは、これを`host`, `port`, `user`, `password`, `dbName`に分解して書くこともできます。

Mikro ORM CLIはデフォルトだとTypeScriptで書いた設定ファイルmikro-orm.config.tsが認識できないので、package.jsonに下記の設定を加えておきます:

```jsonnet:package.json
{
  /*...*/
  "mikro-orm": {
    "useTsNode": true,
    "configPaths": [
      "./src/mikro-orm.config.ts",
      "./dist/mikro-orm.config.js"
    ]
  }
}
```

## エンティティを作る

Mikro ORMを動かすには、最低限1つ以上のエンティティが必要なので、試しにBookエンティティを作ります。エンティティは`entities`ディレクトリを作り、そこに置くようにします。

```entities/book.ts
import { Entity, PrimaryKey, Property } from "@mikro-orm/core";

@Entity()
export class Book {
  @PrimaryKey()
  id!: number;

  @Property()
  title!: string;
}
```

## データベーススキーマを更新する

Bookのエンティティが準備できたら、このエンティティを保存するテーブルをデータベースに作るためにデータベーススキーマの更新をかけます。スキーマの更新にはMikro ORM CLIを使います。

いきなりスキーマ更新をかけてもいいですが、その前にどのようなSQLが実行されるのか確認しておきます:

```
npx mikro-orm schema:update --dump
```

このコマンドを実行すると、どのようにスキーマが更新されるかが分かります:

```sql
set names 'utf8';
set session_replication_role = 'replica';

create table "book" ("id" serial primary key, "title" varchar(255) not null);

set session_replication_role = 'origin';
```

更新内容が良さそうなので、このSQLを実行させます。

```
pnpx mikro-orm schema:update --run
```

psqlでデータベースのテーブルを確認してみると、テーブルが作られていることが分かります:

```
PGPASSWORD=test psql \
    --host 127.0.0.1 \
    --port 5432 \
    --username test \
    --dbname test \
    -c '\d'
```

出力結果:

```
            List of relations
 Schema |    Name     |   Type   | Owner
--------+-------------+----------+-------
 public | book        | table    | test
 public | book_id_seq | sequence | test
(2 rows)
```

## エンティティの保存とロード

データベースの準備ができたので、エンティティの保存(`INSERT`)と、保存したエンティティのロード(`SELECT`)をやってみましょう。

main.tsというファイルを作り、次のようなコードを書きます:

```main.ts
import { MikroORM } from "@mikro-orm/core";
import { Book } from "./entities/book";
import config from "./mikro-orm.config";

async function main() {
  // ORMの初期化
  const orm = await MikroORM.init(config);

  // Bookレポジトリを取得する
  const bookRepository = orm.em.getRepository(Book);

  // Bookエンティティを作る
  const newBook = new Book();
  newBook.title = "サバイバルTypeScript";

  // Bookエンティティを保存する
  await bookRepository.persistAndFlush(newBook);
  console.log({ newBook });
  //=> { newBook: Book { title: 'サバイバルTypeScript', id: 1 } }

  // 保存したBookエンティティを取り出す
  const books = await bookRepository.findAll();
  console.log({ books });
  //=> { books: [ Book { title: 'サバイバルTypeScript', id: 1 } ] }
}

main()
  .then(() => process.exit())
  .catch(console.error);
```

保存のメソッドが`persistAndFlush`となっていますが、Mikro ORMでは、保存予定のエンティティをマークする処理`persist`と実際にエンティティを保存する処理`flush`に分かれているためこのようなネーミングになっています。`persist`と`flush`は別々に呼び出すことができます。`persistAndFlush`はそれら2つを一度に呼び出すためのショートカットです。このへんのメカニズムの詳細は公式ドキュメントを御覧ください: [Working with Entity Manager | MikroORM](https://mikro-orm.io/docs/entity-manager/#persist-and-flush)

このスクリプトはTypeScriptで書いているので、ts-nodeで実行します:

```
pnpx ts-node main.ts
```

実行するとbookテーブルにエンティティが追加されます。

ORMの裏側で実行されたSQLは次の通りになります:

```sql
-- persitAndFlushの操作
BEGIN;
insert into "book" ("title") 
  values ('サバイバルTypeScript') returning "id";
COMMIT;

-- findAllの操作
select "e0".* from "book" as "e0";
```

以上でチュートリアルは完了です。このチュートリアルで作成したコードは[GitHub](https://github.com/suin/nodejs-playground/tree/master/mikro-orm)にホスティングしてあります。