TypeScript で ORM。TypeORM を使って PostgreSQL とやり取りしてみた

Node.js 上で使える O/R マッパーというと、以前 Sequelize というモノを紹介した。

コチラも MySQL や PostgreSQL など、色々な DB と接続できるマッパーだった。

今回見つけた TypeORM という O/R マッパーは、Nest.js にも採用されており、TypeScript の特性をフルに生かして実装できてとても便利なので、いくつか機能を紹介してみる。

目次

Docker-Compose で PostgreSQL 環境を用意する

まずはとりあえず TypeORM を使ってみるため、Docker-Compose を使って PostgreSQL DB を立ち上げてみる。

FROM postgres:11-alpine
ENV LANG ja_JP.utf8
version: '3'
services:
  db:
    build: .
    ports:
      - 5432:5432
    environment:
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: admin

これら2つのファイルを用意して

$ docker-compose up -d

でコンテナを起動し、

$ docker-compose exec db psql -U admin

このように叩くと、PostgreSQL の内部に入れる。

admin=# \l
                             List of databases
   Name    | Owner | Encoding |  Collate   |   Ctype    | Access privileges
-----------+-------+----------+------------+------------+-------------------
 admin     | admin | UTF8     | ja_JP.utf8 | ja_JP.utf8 |
 postgres  | admin | UTF8     | ja_JP.utf8 | ja_JP.utf8 |
 template0 | admin | UTF8     | ja_JP.utf8 | ja_JP.utf8 | =c/admin         +
           |       |          |            |            | admin=CTc/admin
 template1 | admin | UTF8     | ja_JP.utf8 | ja_JP.utf8 | =c/admin         +
           |       |          |            |            | admin=CTc/admin

こんな感じで、DB 一覧を確認できるはずだ。

ports 指定によりホスト側に 5432 ポートで公開してあるので、ホスト側に psql コマンドがあれば

$ PGPASSWORD=admin psql -h localhost -p 5432 -U admin

こんな感じでも DB 接続できる。

TypeORM プロジェクトを作る

次に、TypeORM を叩くためのプロジェクトを作る。

$ npm init -y

$ npm install --save-dev typescript ts-node

# TypeORM と、PostgreSQL 接続用の pg パッケージをインストールする
$ npm install --save typeorm pg

# tsconfig.json を生成する
$ npx tsc --init

tsconfig.json を開き、compilerOptions 配下に experimentalDecoratorslib プロパティを追加する。

{
  "compilerOptions": {
    // ↓ コレを追加
    "experimentalDecorators": true,
    "lib": [
      "esnext",
      "dom"
    ],
    
    // あとはそのまま…
    "module": "commonjs"
    // ……
  }
}

接続情報ファイルを作成する

プロジェクト雛形ができたら、DB との接続情報を .ts ファイルに記述する。

import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions';
const dbConfig: PostgresConnectionOptions = {
  type: 'postgres',
  host: 'localhost',
  port: 5432,
  username: 'admin',
  password: 'admin',
  database: 'admin',
  synchronize: true,
  logging: false,
  entities: ['entities/*.ts']
};
export default dbConfig;

実際のアプリでは 'localhost''admin' などとベタ書きはせず、process.env (環境変数) で注入してやることになるだろう。

エンティティを作成する

続いて、テーブル定義とレコードを表現する Entity クラスを作成する。

ココが TypeORM の真骨頂。デコレータを使ってテーブルの型や特徴を決められると同時に、フィールドの型定義にも適用されるのだ。

なにはともあれ、次のようにファイルを作ってみよう。

import { Column, Entity, PrimaryColumn, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export default class Customer {
  @PrimaryGeneratedColumn()
  public id!: number;
  
  @PrimaryColumn({ type: 'varchar', length: 10 })
  public name: string;
  
  @Column({ type: 'int' })
  public age: number = 0;
  
  constructor(name: string) {
    this.name = name;
  }
}

なんとなく、何をどう指定しているのかは推測が付くだろう。

DB 接続・操作を行うエントリポイントを作成する

最後に、DB 接続と操作を行うエントリポイントを作ってみる。

import { createConnection, getRepository } from 'typeorm';

import dbConfig from './db-config';
import Customer from './entities/customer';

createConnection(dbConfig).then(async (connection) => {
  console.log('Postgres Connected');
  try {
    const customerRepository = getRepository(Customer);
    
    const allCustomers = await customerRepository.find();
    console.log('Select : ', allCustomers);
  }
  catch(error) {
    console.error('Failed : ', error);
  }
  finally {
    await connection.close();
    console.log('Connection Closed');
  }
})
.catch((error) => {
  console.error('Postgres Connection Failed', error);
});

実行してみる

コレでコーディングは完了。いよいよ実行してみる。

まずは DB 接続して、SELECT * FROM customer; 相当の SQL を投げているだけ。実行してみると、次のようになるだろう。

$ npx ts-node exec.ts

Postgres Connected
Select :  []
Connection Closed

PostgreSQL の方はテーブル作成などはしていないが、db-config.ts 内の synchronize: true 指定によって、自動的に Entity に対応するテーブルが作られている。

$ PGPASSWORD=admin psql -h localhost -p 5432 -U admin

# 以下のようにテーブルが作成されている
admin=# SELECT * FROM customer;
 id | name | age
----+------+-----
(0 rows)

さらに exec.ts に追記して、テーブルにデータを投入してみよう。

const customerRepository = getRepository(Customer);

// 以下を追加
const newCustomer = new Customer('Neo');
newCustomer.age = 30;
const savedCustomer = await customerRepository.save(newCustomer);
console.log('Saved : ', savedCustomer);
// 追加ココまで

const allCustomers = await customerRepository.find();

実行してみるとこんな感じ。

$ npx ts-node exec.ts

Postgres Connected
Saved :  Customer { age: 30, name: 'Neo', id: 1 }
Select :  [ Customer { age: 30, name: 'Neo', id: 1 } ]
Connection Closed

登録したデータには ID が自動採番されていることが分かる。

save() メソッドは Upsert として動作するので、Insert と Update のどっちでも良い時に使える。Rails の ActiveRecord みたいなノリだ。

なお、今回は id を自動採番させているので、このまま再実行するとどんどん同じようなデータが Insert されてしまうことに留意。

# スクリプトは変えず再実行
$ npx ts-node exec.ts

Postgres Connected
Saved :  Customer { age: 30, name: 'Neo', id: 2 }
Select :  [
  Customer { age: 30, name: 'Neo', id: 1 },
  Customer { age: 30, name: 'Neo', id: 2 }
]
Connection Closed

今回はココまで

TypeScript のデコレータを大活用している TypeORM。今回はその機能の一部しか紹介しきれなかったが、他にも DB マイグレーションファイルの生成やトランザクション処理など、様々なことができるのでオススメだ。