nestjsとTypeORMに入門した

# はじめに

node.jsのフレームワークで最近勢いのある[nestjs](https://github.com/nestjs/nest)と、これまた人気が出てるORMの[TypeORM](https://github.com/typeorm/typeorm)を触ってみました。
node.jsのフレームワークは薄いものが多いですが、それらとは違って結構多機能で便利に感じたので、備忘録がてら記事にします。

# typescript、 eslint、 prettier、 jest、 ホットリロードなどが初めから設定された状態で開発できる

`nest new <project-name>` とするだけで初めからいろんなツールチェインが入った状態で開発ができます。
あれを入れて、これを入れて、、とやる手間が省けて楽でした。

# class-validatorを使ってリクエストボディのバリデーションが書ける
nestjsではリクエストで飛んでくるボディやクエリストリングなどのことをDTOと読んでます。
このDTOはクラスで定義するのですが、そのクラスのプロパティに[class-validator](https://github.com/typestack/class-validator)のデコレータをペタペタ追加していくことで、柔軟なバリデーションをかけることができます。

```typescript
class CreateParams {
  @IsNotEmpty()
  hoge: string;
}

@Controller('sample')
export class SampleController {
  constructor(private sampleService: SampleService) {}

  // これで、「POST /sample」ではbody内にhoge情報が必須になる
  @Post()
  create(@Body() params: CreateParams) {
    return this.sampleService.create(params);
  }
}
```

またこのバリデーションを有効にするためには以下の記述が必要でした。
参考： https://docs.nestjs.com/pipes

```main.ts
  app.useGlobalPipes(new ValidationPipe());
```

# swaggerを書くのが簡単
swaggerの定義をymlで書くのって割とめんどくさいと思っちゃうんですが、これもデコレーターを使って簡単に書けます。

こんな感じの定義をしておけば、

```typescript

export class FindByIdParams {
  @ApiProperty({
    description: 'sample_idでの検索',
    type: Number,
  })
  @IsNumberString()
  sample_id: number;
}
```

こんな感じで表示されます。
<img width="1418" alt="スクリーンショット 2020-03-20 14.16.04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131625/a92da50b-d4be-e2f8-2bc6-e03a70172b81.png">

他の書き方とかの詳細はこちら： https://docs.nestjs.com/recipes/swagger#openapi-swagger

# トランザクションを貼りたい
nestjsとTypeORMのカスタムリポジトリを使っている状態でトランザクションを貼るにはどうやるのがいいのか探していたら[この記事](https://github.com/typeorm/typeorm/blob/master/docs/custom-repository.md#using-custom-repositories-in-transactions-or-why-custom-repositories-cannot-be-services)が見つかりました。

こんな感じでトランザクションを貼れました。

```typescript
@Injectable()
export class SampleService {
  constructor(private sampleRepository: SampleRepository, private connection: Connection) {}

  create(params: CreateParams): Promise<Sample> {
    return this.connection.transaction(async (manager) => {
      const sampleRepository = manager.getCustomRepository(SampleRepository);

      return sampleRepository.save({
        ...params,
        created_at: new Date(),
        updated_at: new Date(),
      });
    });
  }
}

```

TypeORMはトランザクションを貼るにも[いろんな方法](https://github.com/typeorm/typeorm/blob/master/docs/transactions.md)を提供してくれてます。（3種類くらい？）
ただ、[nestjsの方の記事](https://docs.nestjs.com/techniques/database#transactions)で見つけたのですが、 `@Transaction` のデコレータを使ってtransactionを貼るのはおすすめしないみたいです。
 
<img width="797" alt="スクリーンショット 2020-03-19 14.43.07.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131625/c811a99b-2d62-ab8e-7077-e659c9b2e2ab.png">


# TypeORMのマイグレーションが便利
TypeORMはコードの状態とDBの状態を比較して、そのコードの状態にするためのマイグレーションファイルを自動で作ってくれます。

例えば、こういうsamplesのentityを定義するコードがあるとして、DB上にはsamplesテーブルがなかった場合、

```typescript

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

@Entity('samples')
export class Sample {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  hoge: string;

  @Column()
  created_at: Date;

  @Column()
  updated_at: Date;
}

```

samplesをCREATEするマイグレーションファイルを自動で作ってくれます。

```typescript

import {MigrationInterface, QueryRunner} from "typeorm";

export class CreateSamples1584670197350 implements MigrationInterface {
    name = 'CreateSamples1584670197350'

    public async up(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(`CREATE TABLE "samples" ("id" SERIAL NOT NULL, "hoge" character varying NOT NULL, "created_at" TIMESTAMP NOT NULL, "updated_at" TIMESTAMP NOT NULL, CONSTRAINT "PK_d68b5b3bd25a6851b033fb63444" PRIMARY KEY ("id"))`, undefined);
    }

    public async down(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(`DROP TABLE "samples"`, undefined);
    }

}

```


このCREATE TABLEの実行後に、entityにカラム追加をした場合は、

```typescript

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

@Entity('samples')
export class Sample {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  hoge: string;

  @Column()
  huga: string; // 追加

  @Column()
  created_at: Date;

  @Column()
  updated_at: Date;
}

```

差分となるALTER TABLEのマイグレーションファイルを自動で作ってくれます。

```typescript
import {MigrationInterface, QueryRunner} from "typeorm";

export class AddHugaColumnToSamples1584682435530 implements MigrationInterface {
    name = 'AddHugaColumnToSamples1584682435530'

    public async up(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(`ALTER TABLE "samples" ADD "huga" character varying NOT NULL`, undefined);
    }

    public async down(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(`ALTER TABLE "samples" DROP COLUMN "huga"`, undefined);
    }

}
```

また、[TypeORMの設定項目](https://github.com/typeorm/typeorm/blob/master/docs/connection-options.md#common-connection-options)の `synchronize` を有効にしておくと、コードのentityに何か変更があった場合、マイグレーションを自動実行してくれるようになるので、開発中にマイグレーション周りの作業をほとんどしなくてよくなります。

# 最後に
他にもnestjsは色々機能があるので、いいと思ったのがあれば加筆していきます。

今回試してみたコードはこちらにあげてます。
https://github.com/pokotyan/nestjs-sample