この記事は eeic (東京大学電気電子・電子情報工学科) Advent Calendar 8日目の記事です。
この記事ではNestJSについて書きます.
NestJSは, 最近使われ始めたバックエンド側のフルスタックフレームワークです. ドキュメントのコードはすべてTypeScriptで記述されており, TypeScriptでの開発が前提とされているようですが, JavaScriptとの互換性もあります.
基本的な事項を書こうと思ってたんですが, 同時にNestJS Advent Calendar 2019が進行していて, しかも丁寧に基本的な事項から解説されているのでこの記事ではTips的なことを書いてお茶を濁します
1. nest-routerによるAPIパス構築
NestJSでは, コントローラーにてデコレータを用いたパス指定をします. 例えば/pets/${petId}
をGETで受けたい場合, 次のような実装になります.
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で受けたい場合, 以下のように書く必要があります.
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インターフェースを用います. まずは以下のようなルーティングを決めるオブジェクトを作ります.
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
に組み込みます.
@Module({
imports: [
RouterModule.forRoutes(routes),
PetsModule,
CatsModule,
DogsModule,
GeckoesModule,
],
})
export class AppModule {}
pets以下のコントローラー内にある@Controller
デコレータからパスの情報を消します. こうすることでRouterModuleがroutesに従ってパスを決めてくれます. コントローラーにパスの指定を残していると, 二重にかかってしまいます.
@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
たとえばこれはダメな例です.
@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部分の書き方は以下のような感じです.
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}`;
}
}
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
export class CreateCatDto {
@ApiProperty() // required: trueのプロパティ
name: string;
@ApiProperty()
age: number;
@ApiPropertyOptional() // required: falseのプロパティ
breeder: string;
}
ドキュメントに従って起動ファイルを作成し, アクセスすると以下のようにSwaggerが立ち上がります.
右上のTry it outからリクエストを送ってレスポンスを確認することができます.
で, ここが一番言いたいところなんですが, デコレータの書き方が最近破壊的変更を受けました.
Breaking Changes
- 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' })
- 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
を, 以下のような形で生成してくれます(この辺は他に記事があるので参照してください).
import { Module } from '@nestjs/common';
@Module({})
export class CatsModule {}
ちなみに, nest-cli.json
をいじることによってディレクトリorファイルのソースルートを指定できます.
{
"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.
書くこと思いついたら追加します