ヘッダーの検証のためにNest.jsでInterceptorを実装する

# やりたいこと
到達したリクエストのヘッダーを検証し、Invalidなら401を返し,ValidならAPIの呼び出しを許可する。

昔、AngularでもInterceptorを作ったのですが、それのNest.js版です。

https://qiita.com/SuyamaDaichi/items/03f88aaae592392fbb9a

# 環境
今回は、現時点で最新のNest.js 8系で行います。
説明用にできるだけシンプルに実装します。

基本的には公式ドキュメントに則ってます。

https://docs.nestjs.com/interceptors

# 手順
Nest CLIを使ってファイルを作成します

```
nest g in interceptor/request
```

コマンド一発でファイルと雛形を作ってくれるのはAngular/Nestの良いところの一つです。

https://docs.nestjs.com/cli/usages

それでできるInterceptorの雛形↓
ここにヘッダーを検証する処理を書いていきます。

```typescript
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'
import { Observable } from 'rxjs'

@Injectable()
export class RequestInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle()
  }
}

```

書いたらこんな感じ
```typescript
import {
  CallHandler,
  ExecutionContext,
  Injectable,
  NestInterceptor,
  UnauthorizedException,
} from '@nestjs/common'
import { map, Observable } from 'rxjs'
import { checkToken } from 'src/util/validation'

@Injectable()
export class RequestInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const request = context.switchToHttp().getRequest()
    const token = request.headers['authorization']
    // tokenを検証する処理
    const valid = checkToken(token)

    return next.handle().pipe(
      map((data) => {
        if (!valid) throw new UnauthorizedException()
        return data
      })
    )
  }
}

```

## Interceptorの適用方法
3種類あります。
- クラスレベル
- メソッドレベル
- グローバル

### クラスレベル
コントローラー単位でInterceptorを適用します。
```typescript
@UseInterceptors(new RequestInterceptor()) // Controllerにデコレーターとして呼び出す
@Controller('item')
export class ItemController {
  constructor(private itemService: ItemService) {}

  // 複数のアイテムを取得
  // 価格でのソートに対応
  @Get()
  async getItems(): Promise<item[]> {
    const items = await this.itemService.getItems()
    const response = items
    return response
  }

  // 単一のアイテムを取得
  @Get(':id')
  async getItem(@Param('id', new ParseIntPipe()) itemId: number): Promise<item> {
    const item = await this.itemService.getItem(itemId)
    return item
  }
}

```

### メソッドレベル
メソッド単位でInterceptorを適用します。
```typescript
@Controller('item')
export class ItemController {
  constructor(private itemService: ItemService) {}

  // 複数のアイテムを取得
  // 価格でのソートに対応
  @UseInterceptors(new RequestInterceptor()) // Methodにデコレーターとして呼び出す
  @Get()
  async getItems(): Promise<item[]> {
    const items = await this.itemService.getItems()
    const response = items
    return response
  }

  // 単一のアイテムを取得
  @Get(':id')
  async getItem(@Param('id', new ParseIntPipe()) itemId: number): Promise<item> {
    const item = await this.itemService.getItem(itemId)
    return item
  }
}

```

### グローバルレベル
全てのAPIにInterceptorを適用します。
```typescript
async function bootstrap() {
  const app = await NestFactory.create(AppModule)
  app.useGlobalInterceptors(new RequestInterceptor()) // 追加
  await app.listen(3000)
}
bootstrap()
```

弾かれると、ちゃんと401を返してくれるようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256853/2e56dfca-ac9b-de4d-32fa-bf82bfaea78e.png)