3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

eeic (東京大学工学部電気電子・電子情報工学科)Advent Calendar 2019

Day 8

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

Last updated at Posted at 2019-12-07

この記事は eeic (東京大学電気電子・電子情報工学科) Advent Calendar 8日目の記事です。

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

基本的な事項を書こうと思ってたんですが, 同時にNestJS Advent Calendar 2019が進行していて, しかも丁寧に基本的な事項から解説されているのでこの記事ではTips的なことを書いてお茶を濁します

1. nest-routerによるAPIパス構築

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

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で受けたい場合, 以下のように書く必要があります.

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を使います.

導入

$ npm i -D nest-router

使い方

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

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に組み込みます.

app.module.ts
@Module({
  imports: [
    RouterModule.forRoutes(routes),
    PetsModule,
    CatsModule,
    DogsModule,
    GeckoesModule,
  ],
})
export class AppModule {}

pets以下のコントローラー内にある@Controllerデコレータからパスの情報を消します. こうすることでRouterModuleがroutesに従ってパスを決めてくれます. コントローラーにパスの指定を残していると, 二重にかかってしまいます.

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

たとえばこれはダメな例です.

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を用いてSwaggerを自動生成できます. これがなかなかに便利です.
単にAPIドキュメントとしての機能のみでなく, ちゃんと書けばAPIテスト用ツールとしても機能します.
API部分の書き方は以下のような感じです.

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}`;
  }
}
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;
}

ドキュメントに従って起動ファイルを作成し, アクセスすると以下のようにSwaggerが立ち上がります.
スクリーンショット 2019-12-08 5.11.59.png
スクリーンショット 2019-12-08 5.12.14.png
右上のTry it outからリクエストを送ってレスポンスを確認することができます.
で, ここが一番言いたいところなんですが, デコレータの書き方が最近破壊的変更を受けました.

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という強力な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を, 以下のような形で生成してくれます(この辺は他に記事があるので参照してください).

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

@Module({})
export class CatsModule {}

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

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.

書くこと思いついたら追加します

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?