NestJS+TypeORM+PostgreSQLでWeb API作成

# はじめに

[Node.js/Express/routing-controllers+TypeScript+TypeORM+PostgreSQLでWeb API作成](https://qiita.com/honda28/items/231ebedef4454675b199)で、自分なりに納得いく構成で Web API を作成したが、[NestJS](https://docs.nestjs.com/) なるバックエンドフレームワークの存在を知ってしまったので、そちらも試したところ素晴らしかったので採用。今後 TypeScript での Web API 作成は **NestJS+TypeORM+PostgreSQL** を使っていきたい。実行環境は以下。

- Node.js: 14.17.6
- npm: 6.14.15
- NestJS: 8.0.6
- typeorm: 0.2.37

# NestJS のインストールおよびプロジェクト作成

[First steps | NestJS](https://docs.nestjs.com/first-steps) にしたがって、NestJS をインストールし、プロジェクトを作成する。

```bash
$ npm install -g @nestjs/cli
$ nest new api
```

以下コマンドで動作確認を行う。

```bash
$ cd api
$ npm run start

# 別ターミナルにて
$ curl http://localhost:3000
Hello World!
```

# typeorm のインストール

[TypeORM](https://typeorm.io/#/) にしたがって、以下パッケージをインストールする。PostgreSQL 以外の DB と連携する場合は pg ではなく、適切なパッケージをインストールする。

```bash
$ npm install typeorm pg
$ npm install -g typeorm
$ npm install @nestjs/typeorm
```

# ディレクトリ構成

NestJS プロジェクトのディレクトリ構成（一部抜粋）は controller, entity, service ごとの単位ではなく、モジュール単位で構成している。NestJS は Angular に強くインスパイアされており、以下の photo のようにモジュール単位での構成を前提にしているためである。実際、後ほど実行する `nest generate` コマンドはモジュール単位となるようにファイルが作成される。（たとえば `nest generate module photo` を実行すると、src/photo/photo.module.ts のように photo ディレクトリも合わせて作成される。）

```
.
├── ormconfig.json              # TypeORM 設定ファイル、package.json と同階層に置く
├── package-lock.json
├── package.json
├── src
│   ├── app.module.ts           # 各モジュールを統合する AppModule
│   ├── main.ts                 # アプリケーションのエントリファイル
│   └── photo
│       ├── photo.controller.ts # PhotoController
│       ├── photo.entity.ts     # Photo
│       ├── photo.module.ts     # PhotoModule
│       └── photo.service.ts    # PhotoService
└── tsconfig.json
```

以下、簡単に用語を説明する。

- Entity : モデルを修飾したクラス。このモデルに基づいた DB テーブルを作成でき、データの追加・削除などの操作もできる。
- Controller : ルートとリクエストに対しての動作を定義したクラス。コントローラに注入されたサービスが実際の処理を行う。
- Service : 具体的な処理を定義したクラス。レポジトリを使って、エンティティの管理などを行う。
- Module :  上記のようなクラスをまとめて1つのモジュールとして統合するクラス。結合が疎となるアーキテクチャを実現する。


# Photo モジュールの作成

以下で NestJS + TypeORM + PostgreSQL を用いて Photo API を作成していく。（Photo API は [TypeORM](https://typeorm.io/#/) で例としているものと同じものである。）

大まかな流れは以下の通り。

1. Photo エンティティの作成
1. PhotoService の作成
1. PhotoController の作成
1. PhotoModule の作成
1. AppModule への追加
1. ormconfig.json の作成
1. 動作確認

## 1. Photo エンティティの作成

以下コマンドで、photo.entity.ts ファイルを作成する。（エンティティは nest ではなく typeorm に関わる部分であるため、`nest generate` コマンドが存在しない。先述のディレクトリ構成で述べたように `nest generate` コマンドをあらかじめ実行しておくと photo ディレクトリも作成されるが、説明する順番の都合上ここでは `mkdir` コマンドでディレクトリを作成している。）

```bash
$ mkdir src/photo
$ touch src/photo/photo.entity.ts
```

エンティティクラスは以下のように定義した。モデルのプロパティに対して `@Column` でカラムを指定し、`@Entity` でモデルをエンティティ化している。（詳細な説明は割愛。）

```typescript:photo.entity.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;
}
```

## 2. サービスの作成

以下コマンドで photo.service.ts を作成する。`--no-spec` は photo.service.spec.ts を作成しないためのオプションである。

```bash
$ nest generate service photo --no-spec
```

サービスクラスは以下のように定義した。`@Injectable` により他サービスやコントローラに注入可能にしており、`@InjectRepository` により上記で定義した Photo エンティティのレポジトリを操作可能になる。また具体的な処理として、「写真の追加」と「写真リストの取得」という2つのメソッドを定義している。

```typescript:photo.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Photo } from './photo.entity';

@Injectable()
export class PhotoService {
  constructor(
    @InjectRepository(Photo)
    private readonly photoRepository: Repository<Photo>
  ) { }

  async addPhoto(photo: Photo) {
    this.photoRepository.save(photo);
  }

  async getPhotoList() {
    return await this.photoRepository.find();
  }
}
```

## 3. コントローラの作成

以下コマンドで photo.controller.ts を作成する。

```bash
$ nest generate controller photo --no-spec
```

コントローラクラスは以下のように定義した。`@Controller` の引数にてルートを指定しており、これにより `http://localhost:3000/photo` から始まるリクエストに関する処理を以下メソッドで実行する。また上記で作成した PhotoService を注入し、`@Post/@Get` で修飾することで各メソッドを Post/Get リクエストに対する処理に対応付けている。

```typescript:photo.controller.ts
import { Body, Controller, Get, Post } from '@nestjs/common';
import { Photo } from './photo.entity';
import { PhotoService } from './photo.service';

@Controller('photo')
export class PhotoController {
  constructor(private readonly service: PhotoService) { }

  @Post()
  createPhoto(@Body() photo: Photo) {
    this.service.addPhoto(photo);
  }

  @Get()
  getPhotoList() {
    return this.service.getPhotoList();
  }
}
```

## 4. モジュールの作成

以下コマンドで photo.module.ts を作成する。

```bash
$ nest generate module photo
```

モジュールクラスは以下のように定義した。`@Module` にて、使用するコントローラを `controllers` に、使用するサービスを `providers` に指定している。また `imports` にて、TypeORM と連携して Photo エンティティを扱えるようにしている。

```typescript:photo.module.ts
import { Module } from '@nestjs/common';
import { Photo } from './photo.entity';
import { PhotoController } from './photo.controller';
import { PhotoService } from './photo.service';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [TypeOrmModule.forFeature([Photo])],
  controllers: [PhotoController],
  providers: [PhotoService]
})
export class PhotoModule {}
```

## 5. App モジュールへ PhotoModule の追加

TypeORM と連携するための TypeOrmModule と、上記で定義した PhotoModule を読み込む。TypeOrmModule.forRoot() の引数にて、接続する DB の情報を設定することもできるが、コードが見づらくなるため後述の ormconfig.json にて設定する。

```typescript:app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { PhotoModule } from './photo/photo.module';

@Module({
  imports: [
    TypeOrmModule.forRoot(),
    PhotoModule
  ]
})
export class AppModule {}
```

## 6. ormconfig.json の作成

接続する DB の情報などを設定する ormconfig.json を作成する。コードは以下。

```json:ormconfig.json
{
  "type": "postgres",
  "host": "172.21.0.1",
  "port": 5432,
  "username": "postgres",
  "password": "postgres",
  "database": "test_db",
  "entities": [
    "dist/**/*.entity.js"
  ],
  "synchronize": true
}
```

[Node.js/Express/routing-controllers+TypeScript+TypeORM+PostgreSQLでWeb API作成](https://qiita.com/honda28/items/231ebedef4454675b199)と同様にしたところ、`SyntaxError: Cannot use import statement outside a module` というエラーが発生した。[こちら](https://github.com/nestjs/nest/issues/4283#issuecomment-597987321)を参考に、`"entities": ["dist/**/*.entity.js"]` としたところ解消した。上記リンクでも述べているため、詳細は割愛。

## 7. 動作確認

以下コマンドでアプリケーションを起動する。

```bash
$ npm run start
```

あとは `curl` コマンドや Postman などで、`http://localhost:3000/photo` に対して Post リクエストや Get リクエストを送れば動作が確認できる。

# おわりに

NestJS + TypeORM + PostgreSQL にて、Web API を作成することができた。ひとまずこの組み合わせで納得したので、認証などより複雑な処理を行えるように引き続き勉強していきたい。