NestJS公式ドキュメント翻訳
原文
Controllers | NestJS - A progressive Node.js web framework
コントローラ
コントローラはリクエストを処理し、クライアントにレスポンスを返す役割を担います。
コントローラの目的はアプリケーションに対する特定のリクエストを受信することです。ルーティングはどのコントローラがどのリクエストを受信するかを制御します。多くの場合、各コントローラには複数のルートがあり、異なるルートで異なるアクションを実行できます。
基本的なコントローラを作成するために、クラスとデコレータを使用します。デコレータはクラスを必要なメタデータに関連付け、Nestがルーティングマップを作成できるようにします(リクエストを対応するコントローラに結び付けます)。
ルーティング
次の例では@Controller()
デコレータを使用します。これはコントローラを定義するために必須です。オプションのcats
のルートパスプレフィックスを指定します。@Controller()
デコレータでパスプレフィックスを使用すると、関連するルートのセットを簡単にグループ化して、コードの繰り返しを最小限に抑えることができます。例えば、/customers
ルートの下にあるcustomerエンティティとのやり取りを管理する一連のルートをグループ化することができます。その場合、@Controller()
デコレータでパスプレフィックスcustomers
を指定することで、ファイル内の各ルートに対してパスのその部分を繰り返さなくてよくなります。
import { Controller, Get } from '@nestjs/common';
@Controller('cats')
export class CatsController {
@Get()
findAll(): string {
return 'This action returns all cats';
}
}
CLIを使用すると、$nest g controller cats
コマンドを実行するだけでコントローラを作成できます。
findAll()
メソッドの前の@Get()
HTTPリクエストメソッドデコレータは、NestにHTTPリクエストの特定のエンドポイントのハンドラーを作成するように指示します。エンドポイントは、HTTPリクエストメソッド(この場合はGET)とルート(route)パスに対応します。このルートパスとは何でしょうか?ハンドラーのルートパスは、コントローラに宣言された(オプションの)プレフィックスと、リクエストデコレータで指定されたパスを連結したものです。すべてのルート(cats
)のプレフィックスを宣言し、デコレータにはパス情報を追加していないため、NestはGET /cats
リクエストをこのハンドラーにマッピングします。前述のとおり、パスにはオプションのコントローラパスプレフィックスとリクエストメソッドデコレータで宣言されたパス文字列の両方が含まれます。たとえば、デコレータ@Get('profile')
と組み合わせたcustomers
のパスプレフィックスは、GET /customers/profile
というリクエストのルートマッピングを生成します。
上記の例では、GETリクエストがこのエンドポイントに送信されると、Nestがリクエストをユーザー定義のfindAll()
メソッドにルーティングします。このメソッド名は完全に任意に決められます。当然、ルートをバインドするメソッドを宣言する必要がありますが、Nestにとってメソッド名は重要ではありません。
このメソッドは200ステータスコードと関連するレスポンス(上記の例では単なる文字列)を返します。なぜそうなるのでしょうか?これを説明するために、最初にNestがレスポンスを操作するために2つの異なるオプションを使用するという概念を紹介します。
スタンダード(推奨) | この組み込みメソッドを使用することでリクエストハンドラーがJavaScriptオブジェクトまたは配列を返すときに自動でJSONにシリアライズします。 ただしプリミティブ型(string ,number ,boolean など)を返す場合はシリアライズしません。これにより、レスポンス処理が簡単になります。値を返すだけで、Nestは次の処理を行います。さらに、201を使用するPOSTリクエストを除き、レスポンスのステータスコードはデフォルトで常に200です。ハンドラーレベルで @HttpCode(...) デコレータを追加することで、この動作を簡単に変更できます(ステータスコードを参照)。 |
ライブラリ固有 | ライブラリ固有(Expressなど)のレスポンスオブジェクトが使用できます。これはメソッドハンドラシグネチャの@Res() デコレータを使用して挿入します(findAll(@Res() response) )。これによって、そのオブジェクトによって公開されたネイティブのレスポンス処理メソッドを使用する機能(および責任)を得ることができます。例えば、Expressではresponse.status(200).send() のようなコードでレスポンスを作成することができます。 |
両方のアプローチを同時に使用することはできません。 Nestはハンドラーが@Res()
または@Next()
を使用していることを検出し、ライブラリ固有のオプションを選択したことを示します。両方のアプローチを同時に使用すると、スタンダードアプローチはこの単一のルートに対して自動的に無効になり、期待どおりに機能しなくなります。
リクエストオブジェクト
多くの場合、ハンドラーはクライアントリクエストの詳細にアクセスする必要があります。 Nestは基盤となるプラットフォーム(デフォルトではExpress)のリクエストオブジェクトへのアクセスを提供します。@Req()
デコレータをハンドラーのシグネチャに追加しNestに注入するよう指示することで、リクエストオブジェクトにアクセスすることができます。
import { Controller, Get, Req } from '@nestjs/common';
import { Request } from 'express';
@Controller('cats')
export class CatsController {
@Get()
findAll(@Req() request: Request): string {
return 'This action returns all cats';
}
}
express
の型付け(上記のようにrequest: Request
パラメータ)を利用するには、@types/express
パッケージをインストールします。
リクエストオブジェクトはHTTPリクエストを表し、リクエストクエリ文字列、パラメーター、HTTPヘッダー、およびボディのプロパティがあります(詳細はこちら)。大抵の場合、これらのプロパティを手動で取得する必要はありません。代わりに@Body()
や@Query()
など、すぐに使用できる専用のデコレータを使用します。以下は、提供されるデコレータとそれらが表すプレーンなプラットフォーム固有のオブジェクトのリストです。
@Request() |
req |
@Response(), @Res() ※ |
res |
@Next() |
next |
@Session() |
req.session |
@Param(key?: string) |
req.params / req.params[key]
|
@Body(key?: string) |
req.body / req.body[key]
|
@Query(key?: string) |
req.query / req.query[key]
|
@Headers(name?: string) |
req.headers / req.headers[name]
|
※ 基盤となるHTTPプラットフォーム(Express、Fastify)での型付けとの互換性のために、Nestは@Res()
および@Response()
デコレータを提供します。@Res()
は@Response()
の単なるエイリアスです。どちらも基になるネイティブプラットフォームレスポンスオブジェクトのインターフェースを直接公開します。それらを使用するときは、基礎となるライブラリ(@types/express
など)の型付けもインポートして最大限に活用する必要があります。メソッドハンドラーに@Res()
または@Response()
のいずれかを付与すると、Nestがそのハンドラーのライブラリ固有モードになり、レスポンス管理を担当することに注意してください。その場合、レスポンスオブジェクト(res.json(...)
やres.send(...)
)を呼び出して何らかのレスポンスを発行する必要があります。そうしなければHTTPサーバがハングします。
独自のカスタムデコレータを作成する方法については、この章をご覧ください。
リソース
先ほどcats
リソースを取得するエンドポイントを定義しました(GETルート)。普通は新しいレコードを作成するエンドポイントも提供します。それではPOSTハンドラーを作成しましょう。
import { Controller, Get, Post } from '@nestjs/common';
@Controller('cats')
export class CatsController {
@Post()
create(): string {
return 'This action adds a new cat';
}
@Get()
findAll(): string {
return 'This action returns all cats';
}
}
とても簡単です。 Nestは@Put()
、@Delete()
、@Patch()
、@Options()
、@Head()
、および@All()
のように標準的なHTTPリクエストエンドポイントデコレータを提供します。それぞれが各HTTPリクエストメソッドを表します。
ルートワイルドカード
パターンベースのルートもサポートされています。例えばアスタリスクはワイルドカードとして使用され、任意の文字の組み合わせに一致します。
@Get('ab*cd')
findAll() {
return 'This route uses a wildcard';
}
'ab*cd'
ルートパスは、abcd
、ab_cd
、abecd
などに一致します。文字?
、+
、*
、および()
はルートパスで使用でき、正規表現のサブセットです。ハイフン(-
)とドット(.
)は、文字列ベースのパスによって文字通り解釈されます。
ステータスコード
前述のように、201であるPOSTリクエストを除き、レスポンスステータスコードはデフォルトで常に200です。ハンドラレベルで@HttpCode(...)
デコレータを追加することでこれを簡単に変更できます。
@Post()
@HttpCode(204)
create() {
return 'This action adds a new cat';
}
@nestjs/common
パッケージからHttpCode
をインポートしてください。
多くの場合、ステータスコードは静的ではなくさまざまな要因に依存します。その場合はライブラリ固有のレスポンス(@Res()
を使用して注入)オブジェクトを使用できます(エラーの場合は例外をスローします)。
ヘッダー
カスタムレスポンスヘッダーを指定するには、@Header()
デコレータまたはライブラリ固有のレスポンスオブジェクトを使用できます(そして直接res.header()
を呼び出します)。
@Post()
@Header('Cache-Control', 'none')
create() {
return 'This action adds a new cat';
}
@nestjs/common
パッケージからHeader
をインポートしてください。
リダイレクト
レスポンスを特定のURLにリダイレクトするには、@Redirect()
デコレータまたはライブラリ固有のレスポンスオブジェクトを使用できます(そして直接res.redirect()
を呼び出します)。@Redirect()
は必須のurl
引数と任意のstatusCode
引数を取ります。省略した場合、statusCode
のデフォルトは302
(Found
)です。
@Get()
@Redirect('https://nestjs.com', 301)
HTTPステータスコードまたはリダイレクトURLを動的に決めたい場合があります。これを行うにはルートハンドラーメソッドから次のようなオブジェクトを返します。
{
"url": string,
"statusCode": number
}
返される値は@Redirect()
デコレータに渡される引数をオーバーライドします。
@Get('docs')
@Redirect('https://docs.nestjs.com', 302)
getDocs(@Query('version') version) {
if (version && version === '5') {
return { url: 'https://docs.nestjs.com/v5/' };
}
}
ルートパラメータ
リクエストの一部として動的データを受け入れる必要がある場合、静的パスを使用したルートは機能しません(ID1
のcat
を取得するGET /cats/1
など)。パラメータを使用してルートを定義するには、ルートのパスにルートパラメータトークンを追加してリクエストURLのその位置で動的な値をキャプチャします。以下では@Get()
デコレータのルートパラメータトークンはこの使用法を示しています。この方法で宣言されたルートパラメータには、メソッドシグネチャに追加した@Param()
デコレータを使用してアクセスできます。
@Get(':id')
findOne(@Param() params): string {
console.log(params.id);
return `This action returns a #${params.id} cat`;
}
@Param()
はメソッドパラメーター(上記の例ではparams
)を装飾するために使用され、メソッドの本体内でその装飾されたメソッドパラメーターのプロパティとしてルートパラメーターを使用できるようにします。このコードに見られるように、params.id
を参照することでid
パラメーターにアクセスできます。また、特定のパラメータートークンをデコレータに渡すことで、メソッド内でパラメータ名で直接ルートパラメーターを参照することもできます。
@nestjs/common
パッケージからParam
をインポートしてください。
@Get(':id')
findOne(@Param('id') id): string {
return `This action returns a #${id} cat`;
}
スコープ
さまざまなプログラミング言語のバックグラウンドを持つ人々にとって、Nestではあらゆるものがリクエストで共有されていることは予想外に思うかもしれません。例えばデータベースへのコネクションプール、グローバル状態のシングルトンサービスなどがあります。Node.jsはすべてのリクエストが個別のスレッドによって処理されるリクエスト/レスポンスマルチスレッドステートレスモデルに従っていないことに注意してください。そのため、シングルトンインスタンスを使用することがアプリケーションにとって完全に安全です。
ただし、GraphQLアプリケーションでのリクエストごとのキャッシュ、リクエストの追跡、マルチテナンシーなど、コントローラのリクエストベースのライフタイムが望ましい動作になる場合があります。スコープの制御方法についてはこちらをご覧ください。
非同期性
私たちはモダンJavaScriptが大好きで、データの取得はほとんど非同期であることを知っています。Nestはasync
関数によって非同期をサポートしうまく機能します。
async/await
の詳細はこちら。
すべての非同期関数はPromise
を返す必要があります。これは、Nestが自動的に解決可能な遅延した値を返すことができることを意味します。この例を見てみましょう。
@Get()
async findAll(): Promise<any[]> {
return [];
}
上記のコードは完全に有効です。さらに、NestルートハンドラーはRxJSのobservableなストリームを返すことができるため強力です。 Nestはその下のソースを自動的にサブスクライブし、最後に(ストリームが完了すると)出力された値を取得します。
@Get()
findAll(): Observable<any[]> {
return of([]);
}
上記のアプローチはどちらも機能し、要件に合ったものを使用できます。
リクエストペイロード
先ほどのPOSTルートハンドラーの例はクライアントパラメーターを受け入れませんでした。ここでは@Body()
デコレータを追加して修正しましょう。
ただし、最初に(TypeScriptを使用する場合)DTO(データ転送オブジェクト)スキーマを決定する必要があります。 DTOはネットワークを介してデータを送信する方法を定義するオブジェクトです。TypeScriptインターフェースまたはクラスを使用して、DTOスキーマを決定できます。ここではクラスを使用することをお勧めします。クラスはJavaScript ES6標準の一部であるため、コンパイルされたJavaScriptでは実際のエンティティとして保持されます。一方、TypeScriptインターフェースはトランスパイル時に削除されるため、Nestは実行時にそれらを参照できません。パイプ
などの機能は、実行時に変数のメタタイプにアクセスする可能性があるためこれは重要です。
CreateCatDto
クラスを作成しましょう:
export class CreateCatDto {
readonly name: string;
readonly age: number;
readonly breed: string;
}
基本的なプロパティは3つだけです。その後、CatsController
内で新しく作成されたDTOを使用できます。
@Post()
async create(@Body() createCatDto: CreateCatDto) {
return 'This action adds a new cat';
}
エラーハンドリング
エラーハンドリング(例外処理など)に関する章がここにあります。
完成したリソースサンプル
以下は、いくつかの利用可能なデコレータを使用して基本的なコントローラを作成する例です。このコントローラは内部データにアクセスして操作するためのメソッドをいくつか公開しています。
import { Controller, Get, Query, Post, Body, Put, Param, Delete } from '@nestjs/common';
import { CreateCatDto, UpdateCatDto, ListAllEntities } from './dto';
@Controller('cats')
export class CatsController {
@Post()
create(@Body() createCatDto: CreateCatDto) {
return 'This action adds a new cat';
}
@Get()
findAll(@Query() query: ListAllEntities) {
return `This action returns all cats (limit: ${query.limit} items)`;
}
@Get(':id')
findOne(@Param('id') id: string) {
return `This action returns a #${id} cat`;
}
@Put(':id')
update(@Param('id') id: string, @Body() updateCatDto: UpdateCatDto) {
return `This action updates a #${id} cat`;
}
@Delete(':id')
remove(@Param('id') id: string) {
return `This action removes a #${id} cat`;
}
}
起動して実行
上記のコントローラは完全に定義されていますが、NestはCatsController
が存在することをまだ認識していないため、このクラスのインスタンスは作成されません。
コントローラは常にモジュールに属しているため、@Module()
デコレータ内にコントローラ配列を含めています。ルートAppModule
以外のモジュールはまだ定義していないため、ここでCatsController
を紹介します。
import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';
@Module({
controllers: [CatsController],
})
export class AppModule {}
@Module()
デコレータを使用してモジュールクラスにメタデータを付与したため、Nestはどのコントローラをマウントする必要があるかを簡単に反映できるようになりました。
Appendix:ライブラリ固有のアプローチ
これまでレスポンスを操作するNestの標準的な方法について説明してきました。レスポンスを操作する2つ目の方法は、ライブラリ固有のレスポンスオブジェクトを使用することです。特定のレスポンスオブジェクトを注入するには@Res()
デコレータ
を使用する必要があります。違いを示すためにCatsController
を次のように書き換えましょう。
import { Controller, Get, Post, Res, HttpStatus } from '@nestjs/common';
import { Response } from 'express';
@Controller('cats')
export class CatsController {
@Post()
create(@Res() res: Response) {
res.status(HttpStatus.CREATED).send();
}
@Get()
findAll(@Res() res: Response) {
res.status(HttpStatus.OK).json([]);
}
}
このアプローチは機能し、実際にはレスポンスオブジェクト(ヘッダー操作、ライブラリ固有の機能など)を完全に制御することにより、いくつかの方法で柔軟性を高めることができますが、慎重に使用する必要があります。一般にこのアプローチはあまり明確ではなく、いくつかの欠点があります。主な欠点はインターセプターや@HttpCode()
デコレータなどのNest標準レスポンス処理に依存するNest機能との互換性が失われることです。また、コードはプラットフォームに依存する可能性があり(基になるライブラリがレスポンスオブジェクトに異なるAPIを持っている可能性があるため)、テストが難しくなります(レスポンスオブジェクトをモックする必要があります)。
そのため、可能な場合は常にNest標準アプローチを優先するべきです。