Node.jsフレームワークNestJSによるバックエンド構築Tips

この記事は [eeic (東京大学電気電子・電子情報工学科) Advent Calendar](https://qiita.com/advent-calendar/2019/eeic) 8日目の記事です。

この記事ではNestJSについて書きます.
[NestJS](https://nestjs.com/)は, 最近使われ始めたバックエンド側のフルスタックフレームワークです. ドキュメントのコードはすべてTypeScriptで記述されており, TypeScriptでの開発が前提とされているようですが, JavaScriptとの互換性もあります.

基本的な事項を書こうと思ってたんですが, 同時に[NestJS Advent Calendar 2019](https://qiita.com/advent-calendar/2019/nestjs)が進行していて, しかも丁寧に基本的な事項から解説されているのでこの記事ではTips的なことを書いてお茶を濁します

# 1. nest-routerによるAPIパス構築
NestJSでは, コントローラーにてデコレータを用いたパス指定をします. 例えば```/pets/${petId}```をGETで受けたい場合, 次のような実装になります.

```ts:pets.controller.ts
import { Controller, Param, Headers, Body, Get } from '@nestjs/common';

@Controller('pets') // ここでパスを指定
export class PetsController {
  @Get(':petId') // パスパラメタを受け取る
  async getPet(
    @Param('petId') petId: string,
    @Headers() header: object,
    @Body() petValue: object,
  ) {
    return `petId: ${petId}`;
  }
}
```

```
 $ curl localhost:3000/pets/pet_000
petId: pet_000
```
petsのモジュールを残したまま, pets以下のモジュールとしてcats, dogs, geckoesを作成したい場合, それぞれのコントローラーにてフルのパスを指定してあげる必要があります. 例えば`pets/${petId}/cats/${catId}`をGETで受けたい場合, 以下のように書く必要があります.

```ts:cats.controller.ts
import { Controller, Get, Param, Headers, Body } from '@nestjs/common';

@Controller('pets/:petId/cats') // どんどん長くなる
export class CatsController {
  @Get(':catId')
  async getCat(
    @Param('catId') catId: string,
    @Param('petId') petId: string,
    @Headers() header: object,
    @Body() catValue: object,
  ) {
    return `petId: ${petId} and catId: ${catId}`;
  }
}
```
```
$ curl localhost:3000/pets/pet_000/cats/cat_000
petId: pet_000 and catId: cat_000
```
実際にAPIを作る時, パスが深い/パスが多数分岐するAPIを作りたくなると思います. パスの構築をコントローラーで完結させようとすると, あとからの変更が効かなくなったり, 冗長になったりするかもしれません. そんなときは[nest-router](https://github.com/nestjsx/nest-router)を使います.

## 導入
```
$ npm i -D nest-router
```
## 使い方
nest-router標準のRouterModuleとRoutesインターフェースを用います. まずは以下のようなルーティングを決めるオブジェクトを作ります.

```ts:app.module.ts
import { RouterModule, Routes } from 'nest-router';

const routes: Routes = [
  {
    path: '/pets',
    module: PetsModule,
    children: [
      {
        path: ':petId/cats',
        module: CatsModule,
      },
      {
        path: ':petId/dogs',
        module: DogsModule,
      },
      {
        path: ':petId/geckoes',
        module: GeckoesModule,
      },
    ],
  },
];

```
`path:`で所望のパスを指定, `module:`でパスに対するモジュールを指定, `children:`でそのパス以下にさらにパスを指定する事ができます. `petId`を受け取りたい場合, childrenの方で`:petId/cats`などとする必要があることに注意です(`/pets`でPetsControllerに入れなくなるため).
これを`AppModule`に組み込みます.

```ts:app.module.ts
@Module({
  imports: [
    RouterModule.forRoutes(routes),
    PetsModule,
    CatsModule,
    DogsModule,
    GeckoesModule,
  ],
})
export class AppModule {}
```
pets以下のコントローラー内にある`@Controller`デコレータからパスの情報を消します. こうすることでRouterModuleがroutesに従ってパスを決めてくれます. コントローラーにパスの指定を残していると, 二重にかかってしまいます.

```ts:cats.controller.ts
@Contoller()
export class CatsController {...
```
```
[Nest] 66906   - 12/07/2019, 9:17:43 PM   [RoutesResolver] CatsController {/pets/:petId/cats/}: +0ms
[Nest] 66906   - 12/07/2019, 9:17:43 PM   [RouterExplorer] Mapped {/:catId, GET} route +2ms
```
たとえばこれはダメな例です.

```ts:cats.controller.ts
@Controller('cats')
export class CatsController {...
```
```
[Nest] 66514   - 12/07/2019, 9:14:47 PM   [RoutesResolver] CatsController {/pets/:petId/cats/cats}: +1ms
[Nest] 66514   - 12/07/2019, 9:14:47 PM   [RouterExplorer] Mapped {/:catId, GET} route +2ms
```
これでコントローラーにいちいちフルのパスを書く必要がなくなりました. 長くなるようならルーティングを記述する外のファイルに出してあげればいいと思います.

# 2. Swagger関連デコレータの使い方とバージョンアップによる変更
NestJSでは, [nest-swagger](https://github.com/nestjs/swagger)を用いてSwaggerを自動生成できます. これがなかなかに便利です. 
単にAPIドキュメントとしての機能のみでなく, ちゃんと書けばAPIテスト用ツールとしても機能します.
API部分の書き方は以下のような感じです.

```ts:cats.controller.ts
import { Controller, Get, Param, Headers, Body } from '@nestjs/common';
import { ApiTags, ApiHeader } from '@nestjs/swagger';
import { CreateCatDto } from './createCat.dto';

@ApiTags('Cats') // ここでタグを指定
@ApiHeader({ // Headerを指定
  name: 'authKey',
  description: 'Auth Token',
})
@Controller()
export class CatsController {
  @Get(':catId')
  async getCat(
    @Param('catId') catId: string,
    @Param('petId') petId: string,
    @Param('mammalId') mammalId: string,
    @Headers() header: object,
    @Body() catValue: CreateCatDto,
  ) {
    return `petId: ${petId} and catId: ${catId} and testId: ${mammalId}`;
  }
}
```

```ts:createCat.dto.ts
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';

export class CreateCatDto {
  @ApiProperty() // required: trueのプロパティ
  name: string;

  @ApiProperty()
  age: number;

  @ApiPropertyOptional() // required: falseのプロパティ
  breeder: string;
}
```
[ドキュメント](https://docs.nestjs.com/recipes/swagger)に従って起動ファイルを作成し, アクセスすると以下のようにSwaggerが立ち上がります.
<img width="1201" alt="スクリーンショット 2019-12-08 5.11.59.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532087/b9520e07-bc51-ffa6-48c8-a9130a55837b.png">
<img width="1201" alt="スクリーンショット 2019-12-08 5.12.14.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532087/e95f57b5-77e9-209e-2a51-c69056285027.png">
右上のTry it outからリクエストを送ってレスポンスを確認することができます.
で, ここが一番言いたいところなんですが, デコレータの書き方が最近[破壊的変更](https://github.com/nestjs/swagger/pull/355#issuecomment-547925879)を受けました.

> Breaking Changes
1. The following decorators have been changed:
`@ApiModelProperty` -> `@ApiProperty`
`@ApiModelPropertyOptional` -> `@ApiPropertyOptional`
`@ApiResponseModelProperty` -> `@ApiResponseProperty`
`@ApiImplicitQuery` -> `@ApiQuery`
`@ApiImplicitParam` -> `@ApiParam`
`@ApiImplicitBody` -> `@ApiBody`
`@ApiImplicitHeader` -> `@ApiHeader`
`@ApiOperation({ title: 'test' })` ->`@ApiOperation({ summary: 'test' })`
2. DocumentBuilder breaking changes (aka updated methods signatures):
`addTag`
`addBearerAuth`
`addOAuth2`
`setContactEmail` -> `setContact` (the entire contact information object)
`setHost` has been removed
`setSchemes` has been removed
The following methods have been added:

> `addServer`
`addApiKey`
`addBasicAuth`
`addSecurity`
`addSecurityRequirements`

ここにあるやつだけでなく, クラスのプロパティに関するデコレータも変更になっています(`ApiModelProperty` -> `ApiProperty`).
すっきりとした表現に変わっていますね. 最新バージョンに上げる際はご注意ください.

# 3. NestJSのハマりどころ
不便なところもちょくちょくあります.
### 1. nest-cliで"_"(アンダーバー)を含めたファイルを生成できない
NestJSでは, [nest-cli](https://docs.nestjs.com/cli/overview)という強力なCLIを利用できます. 以下のようなコマンドがあります.

```
$ nest -h
Usage: nest <command> [options]

Options:
  -V, --version                                   output the version number
  -h, --help                                      output usage information

Commands:
  new|n [options] [name]                          Generate Nest application
  build [options] [app]                           Build Nest application
  start [options] [app]                           Build Nest application
  generate|g [options] <schematic> [name] [path]  Generate a Nest element
    Available schematics:
      ┌───────────────┬─────────────┐
      │ name          │ alias       │
      │ application   │ application │
      │ angular-app   │ ng-app      │
      │ class         │ cl          │
      │ configuration │ config      │
      │ controller    │ co          │
      │ decorator     │ d           │
      │ filter        │ f           │
      │ gateway       │ ga          │
      │ guard         │ gu          │
      │ interceptor   │ in          │
      │ interface     │ interface   │
      │ middleware    │ mi          │
      │ module        │ mo          │
      │ pipe          │ pi          │
      │ provider      │ pr          │
      │ resolver      │ r           │
      │ service       │ s           │
      │ library       │ lib         │
      │ sub-app       │ app         │
      └───────────────┴─────────────┘
  info|i                                          Display Nest CLI details
  update|u [options]                              Update Nest dependencies
  add <library> [args...]                         Add a library

```
たとえば`nest new ${projectName}`でサーバーの立ち上げに必要な一通りのファイルを生成できます. また, `nest g mo cats`などとすれば`src/cats`ディレクトリ以下に`cats.module.ts`を, 以下のような形で生成してくれます(この辺は他に記事があるので参照してください).

```ts:src/cats/cats.module.ts
import { Module } from '@nestjs/common';

@Module({})
export class CatsModule {}
```
ちなみに, `nest-cli.json`をいじることによってディレクトリorファイルのソースルートを指定できます.

```json:nest-cli.json
{
  "collection": "@nestjs/schematics",
  "sourceRoot": "src" // ここを変更
}
```
問題はsnake_caseでファイルを生成したいときです.

```
 $ nest g mo snake_test
CREATE /src/snake-test/snake-test.module.ts (86 bytes)
UPDATE /src/app.module.ts (1024 bytes)
```
勝手に"-"(ハイフン)に変換されてます. 他のオプションについても同様です.

```
 $ nest g co snake_test
CREATE /src/snake-test/snake-test.controller.spec.ts (515 bytes)
CREATE /src/snake-test/snake-test.controller.ts (108 bytes)
UPDATE /src/snake-test/snake-test.module.ts (187 bytes)
```
```
 $ nest g s snake_test
CREATE /src/snake-test/snake-test.service.spec.ts (482 bytes)
CREATE /src/snake-test/snake-test.service.ts (93 bytes)
UPDATE /src/snake-test/snake-test.module.ts (277 bytes)
```
この問題に関してのissueが出ています(https://github.com/nestjs/nest-cli/issues/489).
そのうち対応される雰囲気がありますが, それまでは自前でnest-cliの中身をいじるか諦めるしかないっぽいです.
### 2. 
書くこと思いついたら追加します