Controller

コントローラ

コントローラの役目は、HTTPリクエストを受け取りレスポンスを返すことです。

コントローラの目的は、アプリケーションの特定の要求を受け取ることです。 ルーティングによりコントローラがどのHTTPリクエストを受け取るかを制御します。下記の図の様に、各コントローラには複数のルートがあり、異なるルートで異なるアクションを実行することが可能です。

f:id:adrenaline2017:20191130163225p:plain

基本的なコントローラを作成するために、クラスとデコレータを使用します。 デコレータはクラスに必要なメタデータに関連付け、Nestがルーティングマップを作成できるようにします。

import { Get, Controller } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  root(): string {
    return this.appService.root();
  }
}

ルーティング

ルーティングはデータをクライアントまで送信するために、コンピュータネットワーク上のデータの経路を決定する制御のことです。

次の例では、@Controllerデコレータを使用して基本的なコントローラの定義をします。@Controller(’cats’)デコレータの'cats'の部分にルートを宣言することで”http://localhost:3000/cats”へのルーティングが定義されます。以下のコマンドを実行する事でcatsコントローラを作成して見ましょう。

$nest g controller cats

cats.controller.ts

import { Controller, Get } from '@nestjs/common';

//catsを追加
@Controller('cats')
export class CatsController {
  @Get()
  findAll() {
    return 'This action returns all cats';
  }
}

@Getデコレータは、対応するリクエストに@Get以下の内容をマップ(関連づける)するよう、Nestに指示します。

リクエストオブジェクト

多くの端末では、クライアントのリクエストの詳細にアクセスする必要があります。Nestではライブラリ固有のリクエストオブジェクトを使用しています。@ Reqデコレータを使用してNestにリクエストオブジェクト(@Req)をハンドラ(@Getの中)に入れることができます。

cats.controller.ts

import { Controller, Get, Req } from '@nestjs/common';

@Controller('cats')
export class CatsController {
  @Get()
  findAll(@Req() request) {
    return 'This action returns all cats';
  }
}

リクエストオブジェクトはHTTPリクエストを表し、クエリ文字列、パラメータ、HTTPヘッダー、および本文のプロパティを持っています。@Bodyや@Queryなどの専用のデコレータを使用することができます。以下は、デコレータとオブジェクトの比較です。

デコレーター オブジェクト
@Request() req
@Response() res
@Next() next
@Session() req.session
@Param(param?: string) req.params / req.params[param]
@Body(param?: string) req.body / req.body[param]
@Query(param?: string) req.query / req.query[param]
@Headers(param?: string) req.headers / req.headers[param]

HTTPリクエストオブジェクト

catsリソースを取得するためのGETリクエストを定義しました。次にPOSTハンドラを作成しましょう:

cats.controller.ts

import { Controller, Get, Post } from '@nestjs/common';

@Controller('cats')
export class CatsController {

  @Post()
  create() {
    return 'This action adds a new cat';
  }

  @Get()
  findAll() {
    return 'This action returns all cats';
  }
}

これらすべては其々のHTTPリクエストメソッドを表します。

メソッド 役割
@Get() リソースの取得
@Post 子リソースの作成、リソースへのデータ追加
@Put() リソースの更新、リソースの作成
@Delete() リソースの削除
@Patch() リソースの一部を更新
@options() リソースがサポートしているメソッドの取得
@Head() リソースのヘッダ (メタデータの取得)

ルートのワイルドカード

パターンベースのルートもサポートされています。 たとえば、アスタリスクワイルドカード(任意の文字を指示するための特殊な文字記号)として使用され、任意の文字の組み合わせに一致します。

@Get('ab*cd')
findAll() {
  return 'This route uses a wildcard';
}

上の経路パスは、abcd、ab_cd、abecdなどを表しています。 文字 ? + * ()は正規表現の部分集合です。 ハイフン( - )とドット(。)は、文字列ベースのパスによってそのままの意味で解釈されます。

ステータスコード

レスポンスされるHTTPステータスコードはデフォルトで200ですが、POSTレスポンスでは201以外のものです。ハンドラレベルで@HttpCodeデコレータを追加することで、この動作を簡単に変更できます。

@Post()
@HttpCode(204)
create() {
  return 'This action adds a new cat';
}

多くの場合、ステータスコードは静的ではありませんが、さまざまな要因によって異なります。 その場合、ライブラリ固有のレスポンス(@Resを使用して挿入)オブジェクトを使用できます(エラーの場合は例外をスローします)。

ヘッダー

カスタムレスポンスヘッダを指定するには、@Headerデコレータまたはライブラリ固有のレスポンスオブジェクトを使用できます。

@Post()
@Header('Cache-Control', 'none')
create() {
  return 'This action adds a new cat';
}

ルートパラメータ

URLの一部として動的データを受け入れる必要がある場合、静的パスのルートは役立ちません。 パラメータで経路を定義するために、ルートにおけるルートパラメータを直接的に特定することができます。

@Get(':id')
findOne(@Param() params) {
  console.log(params.id);
  return `This action returns a #${params.id} cat`;
}

特定のパラメータを取得するには、その名前をかっこで渡します。

@Get(':id')
findOne(@Param('id') id) {
  return `This action returns a #${id} cat`;
}

Async/await

現代のJavaScriptのデータの抽出はほとんど非同期で行われます。 そのため、Nestはasync関数をサポートしています。

すべてのasync関数はPromiseを返す必要があります。 これは、Nest自身で解決できる遅延値を返すことができることを意味します。 下の例を見てみましょう。

cats.controller.ts

@Get()
async findAll(): Promise<any[]> {
  return [];
}

上記のコードは有効です。 さらにNestのルートハンドラは、RxJS(イベントのハンドリングや非同期処理をラップするライブラリ)がデータを流れとして返すことによりパワフルに動作します。 Nestは自動的に下のソースを一つずつ取り出し、最初に出力された値を取得します。

cats.controller.ts

@Get()
findAll(): Observable<any[]> {
  return of([]);
}

リクエスペイロード

ペイロード:ヘッダやトレーラなどの付加的情報を除いた、データ本体のこと

前のPOSTルートハンドラ(指定したURLアドレスをリクエストすると、コールバックされる)の例では、クライアントパラメータは受け付けていませんでした。 @Body()引数をここに追加して修正します。

しかし、最初に(TypeScriptを使用する場合)DTOの構造を決定する必要があります。 DTOはデータがネットワークを介してどのように送信されるかを定義するオブジェクトです。 TypeScriptインターフェイスを使用するか、単純なクラスを使用してDTOスキーマを判断できます。 驚いたことに、ここでクラスを使用することをお勧めします。

クラスはJavaScript ES6標準の一部です。したがって、それらは単純な関数を表します。 一方TypeScriptインターフェイスが削除されるため、Nestはそれらを参照できません。 これは、パイプのような機能が変数のメタタイプにアクセスするときに追加の可能性を可能にするため重要なのです。

CreateCatDtoクラスを作成しましょう:

creare-cat.dto.ts

export class CreateCatDto {
  readonly name: string;
  readonly age: number;
  readonly breed: string;
}

基本的なプロパティは3つしかありません。 できるだけ純粋なものとして機能させるように常に努めなければならないので、それらはすべて読み取り専用としてマークされています。 その後、CatsController内で新しく作成したスキーマを使用できます。

cats.controller.ts

@Post()
async create(@Body() createCatDto: CreateCatDto) {
  return 'This action adds a new cat';
}

サンプル

いくつかのデコレータを使用して基本コントローラを作成する例を示します。 次のコントローラは、内部データにアクセスして操作するための2つのメソッドを公開しています。

cats.controller.ts

import { Controller, Get, Post, Body, Put, Param, Delete } from '@nestjs/common';

@Controller('cats')
export class CatsController {
  @Post()
  create(@Body() createCatDto) {
    return 'This action adds a new cat';
  }

  @Get()
  findAll(@Query() query) {
    return `This action returns all cats (limit: ${query.limit} items)`;
  }

  @Get(':id')
  findOne(@Param('id') id) {
    return `This action returns a #${id} cat`;
  }

  @Put(':id')
  update(@Param('id') id, @Body() updateCatDto) {
    return `This action updates a #${id} cat`;
  }

  @Delete(':id')
  remove(@Param('id') id) {
    return `This action removes a #${id} cat`;
  }
}

エラーの処理

エラー処理については別の章があります。

立ち上げと実行

上記のコントローラはNestはCatsControllerの存在をまだ知りません。ですので、このクラスのインスタンスは作成されません。

コントローラは常にモジュールに属します。なぜなら、@Moduleデコレータ内にコントローラ配列を保持しているからです。 ルートApplicationModule以外のモジュールはないので、これを使ってCatsControllerを導入します:

app.module.ts

import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';

@Module({
  controllers: [CatsController],
})
export class ApplicationModule {}

私たちはモジュールクラスにメタデータを添付しました。これでNestはどのコントローラをマウントする必要があるのかを簡単に反映することができます。