この記事は 株式会社サイバー・バズ Advent calendar 2021 8日目の記事です。
概要
Nest.jsを使っている時に、front
から受け取ったRequest
を使ってゴネゴネしている時です。
すると、、
「あれ、、、落ちる。。」
「想定した型と合っていない。。。」
ということがありました。
その時の話です。
例
検索処理を例にあげます。
class SearchItemDTO {
minPrice: number
maxPrice: number
page: number
limit: number
}
@Get()
async getItems(@Query() searchItemDTO: SearchItemDTO){
// 検索処理Service
}
以下のRequest
をgetItems
に投げました。
curl 'http://localhost:3001/items?minPrice=3000&maxPrice=10000&page=1&limit=10'
ここでgetItems
に実際に渡ってきたsearchItemDTO
を確認してみると、
全てstring
になっており、SearchItemDTO
の型と乖離していることが分かります。
{ minPrice: '3000', maxPrice: '10000', page: '1', limit: '10' }
minPrice
やmaxPrice
を使ってnumber
前提で計算処理などを書いてしまうと、落ちる危険があります。
Pipes
Nestには、Pipes
というものが存在します。
Pipes
はクライアントからのRequest
をController
に渡す前に処理を挟むことができます。
Documentでも取り上げられているように、変換とバリデーションの二つが主なユースケースのようです。
Built-in pipes
汎用的なPipes
については、あらかじめBuilt-inで@nestjs/common
からimport
して使うことができます。
今回はParseIntPipe
が使えそうです。
ParseIntPipe
を使ってgetItems
を書き換えてみます。
@Get()
async getItems(
@Query('minPrice', ParseIntPipe) minPrice: number,
@Query('maxPrice', ParseIntPipe) maxPrice: number,
@Query('page', ParseIntPipe) page: number,
@Query('limit', ParseIntPipe) limit: number
){
// 検索処理Service
}
再度getItems
に渡ってきたプロパティを確認してみると、
{ minPrice: 3000, maxPrice: 10000, page: 1, limit: 10 }
想定通りnumber
に変換されています。
これで目的は果たせましたが、パラメータが増えるたびにParseIntPipe
を増やす必要があり、
item.controller.ts
ファイルが肥大化してしまうおそれがあります。
ユーザ定義pipe
pipe
は自作することもできます。
まずDTO
のうち
変換前をSearchItemDTO
変換後をSearchItemTransformDTO
として実際の型と乖離がないように再定義してみます。
export class SearchItemDTO {
minPrice: string
maxPrice: string
page: string
limit: string
}
export interface SearchItemTransformDTO {
minPrice: number
maxPrice: number
page: number
limit: number
}
次にpipe
処理です。
import { PipeTransform, Injectable } from '@nestjs/common'
@Injectable()
export class SearchItemPipe implements PipeTransform<SearchItemDTO, SearchItemTransformDTO> {
transform(searchItemDTO: SearchItemDTO): SearchItemTransformDTO {
return Object.entries(searchItemDTO).reduce((acc, cur) => {
const key = cur[0]
const value = ['minPrice', 'maxPrice', 'page', 'limit'].includes(key) ? parseInt(cur[1]) : cur[1]
return { ...acc, [key]: value }
}, {})
}
}
SearchItemPipe
をitem.controller.ts
に反映させます。
@Get()
async getItems(@Query(SearchItemPipe) searchItemTransformDTO: SearchItemTransformDTO){
// 検索処理Service
}
すっきり書けました。
パラメータが増えた場合は、SearchItemPipe
を編集すれば対応できます。
これで定義したDTO
と実際の値との間で乖離が無くなりました。
他にも方法はあるかと思いますが、一例として参考にしていただければと思います。