はじめに
Node.js/Express/routing-controllers+TypeScriptでWeb API作成では、routing-controllers を用いてより構造的なプログラムでシンプルな Web API を作成した。本記事では、TypeORM を用いることで 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 にしたがって、インストールする。
$ npm install typeorm
$ npm install reflect-metadata
$ npm install pg
$ npm install -g typeorm
tsconfig.json についても、ドキュメントにしたがって設定する。以降のコードではデコレータを使用しているのだが、TSConfig Reference によるとデコレータは JavaScript ではまだ承認されていない機能であるため、以下設定が必要なようだ。
{
...
"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 テーブルを作成できる。またデータの追加・削除などの操作も行うことができる。コードは以下。
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;
}
以下で上記コードの簡単な説明を行う。各デコレータの詳細はこちら。
- @Entity : モデルをエンティティ化するデコレータ。オプションでテーブル名を指定可で、デフォルトテーブル名はクラス名(小文字)を使用。
- @Column : カラムを指定するデコレータ。型指定や文字数制限といったオプションが使える。
- @PrimaryGeneratedColumn : プライマリカラムを指定するデコレータ。値は自動生成される。
またドキュメントには書かれていないが、上記では各プロパティに明確な割り当てアサーション ! を使用している。これは Property 'id' has no initializer and is not definitely assigned in the constructor.
のようなエラーを起こさないためである。
コントローラの作成
上記で作成したエンティティを操作できるレポジトリを扱う。以下コードは、レポジトリがもつ標準メソッドを使用してテーブルの情報を取得するようなハンドラを定義している。async/await
を使って、非同期処理もすっきり書ける。
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作成にて、routing-controllers の説明は行っているため、ここでは typeorm のレポジトリのみ説明する。
- getRepository() : 指定したエンティティのレポジトリにアクセス。
- someRepository.find() : レポジトリの標準メソッドの一つ。詳細はこちら。
標準メソッドでは扱えないような複雑な処理をする場合は QueryBuilder()
を用いるらしいが、基本的には標準メソッドのみで十分処理ができそう。
コネクションの設定
ormconfig.json にて、接続する DB の情報やオプションを設定する。このファイルは package.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に接続にて作成した DB に接続している。各種オプションの詳細はこちらを参照することとし、ここでは以下2点のみ説明する。
- entities : connection に使用するエンティティを指定。
- synchronize : アプリケーションを起動するたびにデータベーススキーマを自動作成する必要があるかどうかを指定。本番環境では非推奨。
これら設定をしているため、後述の実行確認ではアプリケーションを起動すると、src/entity/Photo.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');
});
})
動作確認
上記で作成したアプリケーションが正しく動作するか確認する。アプリケーションの実行は以下コマンド。
$ npx ts-node src/app.ts
app listening
テーブルの自動作成
ormconfig.json にて "entities": ["src/entity/*.ts"]
、"synchronize": true
としているため、まずはアプリケーション実行後にテーブルが自動作成されるか psql にて確認する。
# 別ターミナルにて
$ psql -h 127.0.0.1 -p 5432 -U postgres test_db
以下コマンドで、photo テーブルが作成されていること、作成したエンティティ通りのスキーマになっていることがわかる。
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 コマンドで行う。)
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作成 と Node.js/Express/routing-controllers+TypeScriptでWeb API作成に続いて、Node.js/Express/routing-controllers+TypeScript+TypeORM+PostgreSQL で概ねやりたいことは実現できた。
ただ追加で調べていたところ、NestJS なるフレームワークを発見。TypeScript 製のバックエンドフレームワークで、Express をコアとして動作し、routing-controllers のようにデコレータを使いながらコントローラクラスを設計することができ、さらに Angular の思想に則ったモジュールアーキテクチャが採用されているらしく、その他にも利点多数。
フロントエンドで Angular を触っている身からすると、NestJS が Node.js/Express/routing-controllers の上位互換であるように見える。。。
ということで、NestJS+TypeORM+PostgreSQL という構成でも試してみたい。(NestJS は結構メジャーっぽいのになんで先に見つけられなかったんだ。。。)