Node.js/Express/routing-controllers+TypeScript+TypeORM+PostgreSQLでWeb API作成

# はじめに

[Node.js/Express/routing-controllers+TypeScriptでWeb API作成](https://qiita.com/honda28/items/f85cd2308269cb47f789)では、routing-controllers を用いてより構造的なプログラムでシンプルな Web API を作成した。本記事では、[TypeORM](https://typeorm.io/#/) を用いることで PostgreSQL と連携し、DB テーブルから取得したデータを返す Web API を作成する。TypeORM はリレーショナル DB をサポートしており、TypeScript との相性がよいなど、複数のメリットがあるとのこと。実行環境は以下。

- Node.js: 14.17.6
- npm: 6.14.15
- Express: 4.17.1
- TypeScript: 4.4.3
- routing-controllers: 0.9.0
- typeorm: 0.2.37

# TypeORM のインストールおよび tsconfig.json の設定

[TypeORM](https://typeorm.io/#/) にしたがって、インストールする。

```bash
$ npm install typeorm
$ npm install reflect-metadata
$ npm install pg
$ npm install -g typeorm
```

tsconfig.json についても、ドキュメントにしたがって設定する。以降のコードではデコレータを使用しているのだが、[TSConfig Reference](https://www.typescriptlang.org/tsconfig) によるとデコレータは JavaScript ではまだ承認されていない機能であるため、以下設定が必要なようだ。

```json:tsconfig.json
{
  ...
  "emitDecoratorMetadata": true,
  "experimentalDecorators": true,
  ...
}
```

# TypeORM の使用方法

ドキュメントに沿って順に進める。Quick Start としてプロジェクトを一気に作成することもできるようだが、ここでは一つずつファイルを作成して進める。ディレクトリ構成は以下のようになっている。

```
.
├── ormconfig.json              # connection 設定を定義
├── package-lock.json
├── package.json
├── src
│   ├── app.ts                  # connection 作成・実行
│   ├── controllers             # route と handler を定義
│   │   └── photoController.ts
│   ├── entity                  # entity を定義
│   │   └── Photo.ts
│   └── migration               # migration ファイル置き場、omrconfig.json で指定
└── tsconfig.json
```

## エンティティの作成

エンティティはモデルを `@Entity` デコレータで修飾したもので、このモデルに基づいた DB テーブルを作成できる。またデータの追加・削除などの操作も行うことができる。コードは以下。

```typescript:src/entity/Photo.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class Photo {

    @PrimaryGeneratedColumn()
    id!: number;

    @Column({
        length: 100
    })
    name!: string;

    @Column('text')
    description!: string;

    @Column()
    filename!: string;

    @Column()
    views!: number;

    @Column()
    isPublished!: boolean;
}
```

以下で上記コードの簡単な説明を行う。各デコレータの詳細は[こちら](https://typeorm.io/#/decorator-reference)。

- @Entity : モデルをエンティティ化するデコレータ。オプションでテーブル名を指定可で、デフォルトテーブル名はクラス名（小文字）を使用。
- @Column : カラムを指定するデコレータ。型指定や文字数制限といったオプションが使える。
- @PrimaryGeneratedColumn : プライマリカラムを指定するデコレータ。値は自動生成される。

またドキュメントには書かれていないが、上記では各プロパティに**明確な割り当てアサーション !** を使用している。これは `Property 'id' has no initializer and is not definitely assigned in the constructor.` のようなエラーを起こさないためである。

## コントローラの作成

上記で作成したエンティティを操作できる**レポジトリ**を扱う。以下コードは、レポジトリがもつ標準メソッドを使用してテーブルの情報を取得するようなハンドラを定義している。`async/await` を使って、非同期処理もすっきり書ける。

```typescript:src/controllers/photoController.ts
import { Get, JsonController } from 'routing-controllers';
import { getRepository } from 'typeorm';
import { Photo } from '../entity/Photo';

@JsonController()
export class PhotoController {
  private photoRepository = getRepository(Photo);

  @Get('/photos')
  async getAll() {
    const photos = await this.photoRepository.find();

    return photos;
  }
}
```

[Node.js/Express/routing-controllers+TypeScriptでWeb API作成](https://qiita.com/honda28/items/f85cd2308269cb47f789)にて、routing-controllers の説明は行っているため、ここでは typeorm のレポジトリのみ説明する。

- getRepository() : 指定したエンティティのレポジトリにアクセス。
- someRepository.find() : レポジトリの標準メソッドの一つ。詳細は[こちら](https://typeorm.io/#/find-options)。

標準メソッドでは扱えないような複雑な処理をする場合は `QueryBuilder()` を用いるらしいが、基本的には標準メソッドのみで十分処理ができそう。

## コネクションの設定

ormconfig.json にて、接続する DB の情報やオプションを設定する。このファイルは package.json と同階層に置くこと。コードは以下。

```json:ormconfig.json
{
  "type": "postgres",
  "host": "172.17.0.1",
  "port": 5432,
  "username": "postgres",
  "password": "postgres",
  "database": "test_db",
  "entities": [
    "src/entity/*.ts"
  ],
  "migrations": ["src/migration/*.ts"],
  "cli": {
    "entitiesDir": "src/entity",
    "migrationsDir": "src/migration"
  },
  "synchronize": true
}
```

今回は [TypeScriptでDocker上に構築したPostgreSQLに接続](https://qiita.com/honda28/items/30151aeba8217d46e9bf)にて作成した DB に接続している。各種オプションの詳細は[こちら](https://github.com/typeorm/typeorm/blob/master/docs/connection-options.md)を参照することとし、ここでは以下2点のみ説明する。

- entities : connection に使用するエンティティを指定。
- synchronize : アプリケーションを起動するたびにデータベーススキーマを自動作成する必要があるかどうかを指定。本番環境では非推奨。

これら設定をしているため、後述の実行確認ではアプリケーションを起動すると、`src/entity/Photo.ts` をもとに自動的にテーブルが作成される。

## コネクションの作成

上記で設定した接続情報をもとに、コネクションの作成を行う。

```typescript:src/app.ts
import 'reflect-metadata';
import { createExpressServer } from 'routing-controllers';
import { createConnection } from 'typeorm';
import { PhotoController } from './controllers/photoController';

createConnection().then(() => {
  const app = createExpressServer({
    controllers: [PhotoController]
  });
  const port = 3000;

  app.listen(port, () => {
    console.log('app listening');
  });
})
```

## 動作確認

上記で作成したアプリケーションが正しく動作するか確認する。アプリケーションの実行は以下コマンド。

```bash
$ npx ts-node src/app.ts
app listening
```

### テーブルの自動作成

ormconfig.json にて `"entities": ["src/entity/*.ts"]`、`"synchronize": true` としているため、まずはアプリケーション実行後にテーブルが自動作成されるか psql にて確認する。

```bash
# 別ターミナルにて
$ psql -h 127.0.0.1 -p 5432 -U postgres test_db
```

以下コマンドで、photo テーブルが作成されていること、作成したエンティティ通りのスキーマになっていることがわかる。

```sql
test_db=# \dt
         List of relations
 Schema | Name  | Type  |  Owner
--------+-------+-------+----------
 public | photo | table | postgres
(1 rows)

test_db=# \d photo
                                      Table "public.photo"
   Column    |          Type          | Collation | Nullable |              Default
-------------+------------------------+-----------+----------+-----------------------------------
 id          | integer                |           | not null | nextval('photo_id_seq'::regclass)
 name        | character varying(100) |           | not null |
 description | text                   |           | not null |
 filename    | character varying      |           | not null |
 views       | integer                |           | not null |
 isPublished | boolean                |           | not null |
Indexes:
    "PK_723fa50bf70dcfd06fb5a44d4ff" PRIMARY KEY, btree (id)
```

### データの取得

テーブルの作成は確認できたため、続いて HTTP リクエストを送って正しくデータが返ってくるかを確認する。現状テーブル内は空なので、先ほどアクセスしたテーブルにデータを追加する。（データの追加も TypeORM で行えるが、ここは psql コマンドで行う。）

```sql
test_db=# insert into photo values
(1, 'A', 'Sea',      'sea_photo',     100, true),
(2, 'B', 'River',    'river_photo',    10, false),
(3, 'C', 'Mountain', 'mountain_photo', 50, true);
INSERT 0 3

test_db=# select * from photo;
 id | name | description |    filename    | views | isPublished
----+------+-------------+----------------+-------+-------------
  1 | A    | Sea         | sea_photo      |   100 | t
  2 | B    | River       | river_photo    |    10 | f
  3 | C    | Mountain    | mountain_photo |    50 | t
(3 rows)
```

`$ curl http://localhost:3000/photos/` を実行すると、上記で登録したデータが返ってくることが確認できる。

# おわりに

[Node.js/Express+TypeScriptでWeb API作成](https://qiita.com/honda28/items/9b0410ecbb1951fd6b00) と [Node.js/Express/routing-controllers+TypeScriptでWeb API作成](https://qiita.com/honda28/items/f85cd2308269cb47f789)に続いて、**Node.js/Express/routing-controllers+TypeScript+TypeORM+PostgreSQL** で概ねやりたいことは実現できた。
ただ追加で調べていたところ、[NestJS](https://docs.nestjs.com/) なるフレームワークを発見。TypeScript 製のバックエンドフレームワークで、Express をコアとして動作し、routing-controllers のようにデコレータを使いながらコントローラクラスを設計することができ、さらに Angular の思想に則ったモジュールアーキテクチャが採用されているらしく、その他にも利点多数。
フロントエンドで Angular を触っている身からすると、**NestJS が Node.js/Express/routing-controllers の上位互換**であるように見える。。。
ということで、NestJS+TypeORM+PostgreSQL という構成でも試してみたい。（NestJS は結構メジャーっぽいのになんで先に見つけられなかったんだ。。。）