【NestJS】ガチ入門【その２： Controller 編】

# はじめに

前回（ [【NestJS】ガチ入門【その１：NestJSの概要とプロジェクト作成・立ち上げ】](https://qiita.com/togswr/items/b901dfb4418780b81bbe)）の続きです。

今回は NestJS の Controller についてまとめていきます。
前回同様、公式ドキュメントに沿ってまとめていきます。

https://docs.nestjs.com/controllers

# Controller とは

Controller は、入ってくるリクエストを処理しクライアントにレスポンスを返すという責務を持ちます。

Controller の目的は、アプリケーションに送られてくる特定のリクエストを受け取ることです。
Routing 機構がコントローラーで受け取るリクエストを制御します。

各 Controller は1つ以上の route を持ち、それぞれの route はそれぞれの動きをすることができます。

Contorller を作成するには、クラスと Decorator を使用します。
Decorator はクラスとメタデータを紐づけ、 Nest にルーティングマップを作成させることができるようになります。
ルーティングマップでは、リクエストとそれに対応する Controller を紐づけます。

:::note info
[`nest g resource [name]`](https://docs.nestjs.com/recipes/crud-generator#generating-a-new-resource) で [validation](https://docs.nestjs.com/techniques/validation) が実装された状態の Controller を生成できます。
:::

## デフォルトで生成される Controller を見てみる

以上の説明だけではイメージがしづらいと思うので、デフォルトで生成される Controller を少し見てみます。
デフォルトでは以下のような Controller が生成されていました。
以上の説明にのっとると、

- `@Controller()` と `class AppController` で Controller を定義
- `@Get()` と `getHello()` で route とその挙動を定義

していることがわかります。

デフォルトでは `/src` 直下にこの Controller が生成されます。
そのため、 `npm run start:dev` などで開発サーバーを立ち上げたあと、 `http://localhost:3000/` に GET リクエストを送信すると、 `getHello()` メソッドが呼ばれることになります。

それ以外の要素として、 Service がありますが、こちらについてはまた別途説明します。

```typescript:/src/app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

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

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

# Routing

公式ドキュメントの `cats.controller.ts` を使って Routing について説明していきます。

まず前述のとおり、 Controller の定義時には `@Controller()` デコレータをつける必要があります。
この例では `@Controller()` デコレータの第一引数に `'cats'` を渡しています。
これは path prefix と呼ばれるものです。
`'cats'` という path prefix を渡すことで、 `/cats` にて、 `CatsController` 内のHTTPリクエストメソッドデコレータ（ `@Get()` など）が付与されたメソッドを呼び出すことができるようになります。

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

@Controller('cats')
export class CatsController {
  @Get()
  findAll(): string {
    return 'This action returns all cats';
  }
}
```

:::note
`$ nest g controller [name]` で CLI から Controller を生成できます。
:::

`@Get` HTTP リクエストメソッドデコレータは特定エンドポイントの HTTP リクエストハンドラーを作るよう Nest に指示します。
このエンドポイントは HTTP リクエストメソッドデコレータの HTTP リクエストメソッドおよびデコレータの引数で指定されたルートパスに対応します。

ルートパスとは、 コントローラーデコレータで定義されたプレフィックスと、 HTTP リクエストメソッドデコレータで与えられたパスを結合して決定されます。

この `CatsController` の例では、 `CatsController` で定義されるすべてのルートのパスプレフィックスとして `cats` を指定しており、メソッドデコレータではパスを指定していないため、 `GET /cats` エンドポイントが作成されることになります。
例えば、 `CatsController` に `@Get('breed')` ルートが定義された場合 `GET /cats/breed` にマッピングされます。

なお、 `@Get()` デコレータなどを付与するメソッド名（上の例では `findAll()` など）は自由に決めることができます。

# Request object

メソッドハンドラーから Express や Fastify などのプラットフォーム[^platform]で定義されたリクエストオブジェクトにアクセスできます。
リクエストメソッドへのアクセスは、 `@Req` デコレータを使ってメソッドのシグネチャに注入することで可能です。

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

他にも標準で以下のようなデコレータを使用して、以下のような HTTP リクエストのオブジェクトにアクセスできます。
以下は一部抜粋して記載しておりますので、詳しくは[こちら](https://docs.nestjs.com/controllers#request-object)をご参照ください。

|デコレータ|オブジェクト|
|---|---|
| `@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(key?: string)` |`req.headers` / `req.headers[name]`|

# Resources

リソース（データ）を操作するため [^resource] の HTTP リクエストメソッドを受けるエンドポイントは `@Get()` デコレータ以外にも、 `@Post()` , `@Put()` , `@Delete()` , `@Patch()` , `@Options()` で定義できる。

また、 `@All()` デコレータを使うことでこれら全ての HTTP リクエストメソッドを扱うエンドポイントを定義できる。

# Route wildcards

以下のようにすることでルートパスをワイルドカードで定義できる。
以下の例では、 `abcd` 以外にも `ab_cd` , `abecd` などにマッチする。

ワイルドカードは正規表現のうち、 `*` , `?` , `+` , `()` が使える。
`-` と `.` は文字列ベースのパスとして解釈される。

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

:::note warn
ワイルドカードをパスの文字列間に使用できるのは使用プラットフォームが express の場合のみ。
:::

# Status code

ステータスコードはデフォルトで `200` が返されます。
ただし、 POST リクエストに対しては `201` が返されます。
`@HttpCode(...)` デコレータを使うことで、任意のステータスコードを返すこともできます。

以下は、デフォルトで `201` を返す設定を `204` (No Content) を返す設定に変更しています。

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

状況に応じてステータスコードを変えたい場合は、 `@Res()` などをつかってハンドラーメソッドの引数に使用プラットフォーム（ express など）の `response` オブジェクトを注入し、使用します。

```ts
@Post()
@HttpCode(204)
create(@Res() res: Response) {
  if (...) {
    res.status(500).send('Cat nap time. Try later!')
  }

  return 'This action adds a new cat';
}
```

https://qiita.com/ms2geki/items/c2d3b3f38aabb00052a8

https://expressjs.com/en/api.html#res.status

また、エラーコードを返す場合は、以下のように例外を投げることで、 HTTP ステータスコードを変えることも可能です。

```ts
@Post()
@HttpCode(204)
create(@Res() res: Response) {
  if (...) {
    throw new InternalServerErrorException('Cat nap time. Try later!');
  }

  return 'This action adds a new cat';
}
```

詳しくは公式ドキュメントの [Exception filters](https://docs.nestjs.com/exception-filters) セクションをご確認ください。

# Headers

レスポンスヘッダーを指定するには `@Header()` もしくは、 `@Res()` デコレータを使用します。

- `@Header()` デコレータを使用する例

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

- `@Res()` デコレータを使用する例

```ts
@Post()
create(@Res() res: Response) {
  res.set('Cache-Control', 'none');
  res.status(201).send('This action adds a new cat');
}
```

# Redirection

こちらも `@Redirect()` デコレータもしくは、 `@Res()` デコレータを使用します。
`@Redirect()` では、 `url` と `statusCode` の2つの引数を取り、どちらも省略可能です。
`statusCode` のデフォルト値は `302` となっています。

```ts
@Get()
@Redict('https://nestjs.com', 301)
```

```ts
@Get()
find(@Res res: Response) {
  if (...) {
    res.redirect(301, 'https://nestjs.com');
  }
}
```

https://expressjs.com/ja/4x/api.html#res.redirect

`@Redirect()` デコレータに指定したいずれの引数も以下のようにして上書きすることができます。
以下の例では、クエリパラメータに `?version=5` のような文字列が入っている場合、 `/v5/` というパスへのリダイレクトに変更されています。
また、 `Response` オブジェクトを使用しても、同様に挙動の変更が可能です。

```ts
@Get('docs')
@Redirect('https://docs.nestjs.com', 302)
getDocs(@Query('version') version: string, @Res() res: Response) {
  if (version && version === '5') {
    return { url: 'https://docs.nestjs.com/v5/' };
  }

  if (version && version == '2') {
    res.redirect('https://docs.nestjs.com/v2/');
  }
}
```

# Route Parameters

`GET /cats/1` の `1` 部分に ID などの動的データを指定したい場合は、静的ルートでは動きません。
そのような場合は、 `@Get()` などのリクエストメソッドデコレータの引数に `:id` といったルートパラメータトークンをリクエストURLの設置したい箇所に追加することで動的ルートを定義できます。
また `@Param()` デコレータをしようすることで、動的データをメソッドハンドラーの引数として受け取ることができます。

:::note
動的ルートは静的ルートよりうしろに定義する必要があります。
これにより、静的ルートの前に動的ルートにトラフィックが流れてしまうことを防ぎます。
:::

以下の例では、パターン1では、 `@Param() params` でパラメータを全て受け取っていますが、パターン2のように `@Param('id') id` とすることで、個別にパラメータを受け取ることも可能です。

```ts
// パターン1
@Get(':id')
findOne(@Param() params: any): string {
  console.log(params.id);
  return `This action returns a #${params.id} cat`;
}

// パターン2
@Get(':id')
findOne(@Param('id') id: string): string {
  return `This action returns a #${id} cat`;
}
```

# Sub-Domain Routing

`@Controller` デコレータの `host` オプションにホスト名を指定することで、HTTPリクエストを行うホスト名のマッチングを行うことができます。

例えば、以下の例では `curl http://localhost:3000/` としてアクセスすると 404 エラーが返ります。一方、 `-H 'Host:admin.example.com'` オプションを `curl` コマンドにつけることで `Admin page` というレスポンスを得ることができます。

```ts
@Controller({ host: 'admin.example.com' })
export class AdminController {
  @Get()
  index(): string {
    return 'Admin page';
  }
}
```

また、ルートパスと同様に、引数で指定するホスト名文字列にトークンを使用することで、その箇所を動的なホスト名として扱うことができます。
ホストパラメータはメソッドハンドラーにて `@HostParam()` デコレータを使うことでアクセスすることができるようになります。

```ts
@Controller({ host: ':account.example.com' })
export class AccountController {
  @Get()
  getInfo(@HostParam('account') account: string) {
    return account;
  }
}
```

# Scopes

NestJS では入ってくるリクエストを処理するにあたって、ほとんどのものが共有されます。
例えば、DBへのコネクションプールについても、シングルトンのサービスがグローバルステートで共有されます。
Node.js では、各リクエストは異なるスレッドで処理されるため、シングルトンインスタンスを使用することは完全に安全です。

ただし、 GraphQL を扱うアプリケーションでリクエストごとにキャッシュを行いたい、リクエストの追跡をしたい、アプリケーションをマルチテナントに対応したいなどの、リクエストベースのライフタイムを持つようなケースがあります。
このような場合のスコープの扱いについては、以下の詳しい内容で見ていきます。

- [Injection scopes | NestJS - A progressive Node.js framework](https://docs.nestjs.com/fundamentals/injection-scopes)

# Asynchronicity

メソッドハンドラーは、 `Promise` を返す（ async 関数にする）ことができます。
これにより、メソッドハンドラー内で非同期処理を行うことができます。
試しに以下のように 5 秒待つ処理を入れて、 `time` コマンドで見ると、約 5 秒後にレスポンスが得られることがわかります。

```ts
@Get()
async findAll(): Promise<any[]> {
  // 5秒待つ
  await new Promise((resolve) => setTimeout(resolve, 5000));
  return [];
} // 0.02s user 0.01s system 0% cpu 5.037 total
```

また、 RxJS という JavaScript で非同期処理を便利に扱うライブラリのコアな型である `Observable` 型を使用することもできます。

:::note
以下で使用している [`of`](https://rxjs-dev.firebaseapp.com/api/index/function/of) は、引数で渡した値を `Observable<T>` に変換してくれる関数です。
:::

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

RxJS については、以下をご参照ください。

https://rxjs-dev.firebaseapp.com/guide/overview

# Request payloads

以下の POST ルートハンドラはクライアントから何もデータを受け取っていません。

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

`@Body()` デコレータを追加することでクライアントからのデータを受け取ることも可能ですが、その前に **DTO** (Data Transfer Object) スキーマを定義します。
DTO とは、データがどのようにネットワーク上に送られるかを定義したオブジェクトです。

DTO スキーマは TypeScript のインターフェイスを使っても定義できますが、ここではシンプルなクラスとして定義することをおすすめします。
なぜなら、 JavaScript にトランスパイルされた際、 TypeScript のインターフェイスは削除されてしまからです。
この機能は、パイプ （Pipes) が実行時に値のメタタイプにアクセスできるようにするために重要になります。

以下が DTO の例です。

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

また、以下のようにして DTO を経由して `@Body()` からクライアントからのデータを取得することができます。

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

:::note
`ValidationPipe` を使うことでメソッドハンドラーに渡したくないプロパティをホワイトリスト形式で取り除くことができます。
オブジェクトからはホワイトリストに指定されたプロパティのみ含まれ、指定されなかったプロパティは削除されます。
上記の `CreateCatDto` の例では、 `name` , `age` , `breed` プロパティがホワイトリストにあたります。
詳しい解説は[こちら](https://docs.nestjs.com/techniques/validation#stripping-properties)をご参照ください。
:::

# Handling errors

エラーハンドリングについては、別の章に分けられています。
[こちら](https://docs.nestjs.com/exception-filters)をご参照ください

# Full resourcec sample

以下から基本的なコントローラーをつくるために使用可能なデコレータを使用した例を見ることができます。

- [Full resource sample# | Controllers | NestJS - A progressive Node.js framework](https://docs.nestjs.com/controllers#full-resource-sample)

また、以下にもサンプルコードがあるのを見つけたので、こちらもリンクを記載しておきます。
こちらは、上記サンプルとは違い、今後出てくる service のコードも含まれています。

- [nest/sample/01-cats-app/src/cats at master · nestjs/nest](https://github.com/nestjs/nest/tree/master/sample/01-cats-app/src/cats)

`cats.service.ts` では、サービス自体が内部プロパティとして `cats` 配列を持っており、そこにデータを書き込んだり、データの取得ができるようになっているようです。

```ts:cats.service.ts
import { Injectable } from '@nestjs/common';
import { Cat } from './interfaces/cat.interface';

@Injectable()
export class CatsService {
  private readonly cats: Cat[] = [];

  create(cat: Cat) {
    this.cats.push(cat);
  }

  findAll(): Cat[] {
    return this.cats;
  }
}
```

:::note
Nest CLI をつ使うことでボイラープレートコードを自動生成することができます。くわしくは[こちら](https://docs.nestjs.com/recipes/crud-generator)をご参照ください。
:::

# Getting up and running

上記のようにコントローラーを完全に定義しただけでは、 NestJS は `CatsController` の存在を認識しておらず、コントローラークラスのインスタンスは生成されません。
以下のように `AppModule` の `@Module()` デコレータの `controllers` 配列に `CatsController` を指定することでモジュールに属させることができます。

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

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

今回は `AppModule` 以外を定義していないためこのような指定になっていますが、 `CatsModule` に `CatsController` を指定し、 `AppModule` に `imports` 配列で `CatsModule` を指定することも可能です。

このように `@Module()` デコレータを使ってメタデータをモジュールクラスに付与することで、 NestJS がマウントすべき Controller を反映できます。

# まとめ
本稿では NestJS の Controller について見ていきました。
ほぼ公式ドキュメントをなぞるだけになってしまい、自分なりの訳にもなっているので不正確な箇所もあるかもしれませんので、適宜公式ドキュメントも一緒にご参照ください。
次回移行も同じような形式で NestJS の機能をみていきます :bow:

[^platform]: 本稿では、 Express や Fastify などの Web フレームワークのことをプラットフォームと呼びます。公式ドキュメントでは、 `library-specific` などと書かれている箇所は文脈にあわせて `platform-specific` と置き換えて読んでいきます。
[^resource]: [公式ドキュメントの Resource セクション](https://docs.nestjs.com/controllers#resources) にならって Resource としていますが、その意味が明確に書かれていなかったため、筆者の解釈になります。