NestJS
リソース
やること
NestJSの全体像を掴む。Overviewを中心に読む。
メモ
NestJSはTypeScript製のウェブアプリフレームワーク
nest g controller catsのようなコマンドで雛形生成できる。Rails Generateみたいな感じ。
Controllers
Controllerはroutesと一体になっている。
デコレータを使ってリクエストやレスポンスのあらゆる値を変えたりできるっぽい
レスポンスはArrayやObjectを返すと勝手にserializeされるのでコードがシンプルになりやすそう。もちろんExpressやFastifyのレスポンス機構を使うこともできる。
Scopeとは
libraryのインスタンスをどのレベルまで使い回すかというポリシー。DEFAULTはシングルトン、REQUESTはリクエストごとに、TRANSIENTはControllerやServiceレベルで初期化して使い回される。
DTOとは
requestのpayloadsをクラスベースで表現したもの。Controllerに入ってくる全てのRequestを型で保護できる。
code:cats.controller.ts
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;
}
}
Providers
https://scrapbox.io/files/61cab5bcafb693001d5c72ee.png
NestJSの全てのクラス(services, repositories, factories, helpersなど)はProviderとして扱われる。Providerは依存注入できる。各処理をSOLIDにしたがって記述して設計しやすくなる。 TypeScriptの普通のクラスで@Injectable()のアノテーションをつけるだけでDI可能なProviderになる。
このDI周りのコンセプトはAngularから大きく影響を受けているらしい
constructorで注入するパターンとPropertyで注入するパターンがある。
Modules
https://scrapbox.io/files/61cab5a41c8754001d970aba.png
ModuleはProviderや他の依存関係を解決する機構。Moduleは他のModuleをimportして全体として親子関係を作る。
Moduleはexportすることで他のModuleから利用できるようにしたりできる。ただしModule配下のScopeに限られる。グローバルスコープでアクセスできるわけではない(@Global()を使うとできるけど)。
Middleware
RequestとResponseの間に差し込める処理。@Injectable()で書いてModuleのconstructorで定義する。
code:ts:logger.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
console.log(Middleware: ${req.headers.host});
next();
}
}
code:ts:app.module.ts
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { LoggerMiddleware } from './logger.middleware';
import { TaskModule } from './task/task.module';
@Module({
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(LoggerMiddleware).forRoutes('task');
}
}
Exception filters
https://scrapbox.io/files/61cac1760da71d001d6d8e00.png
例外処理を担うレイヤー。何もカスタムしてなければデフォルトで500のjsonレスポンスを返す。
ControllerでHttpExceptionをresponseとstatusを伴って返すことで自動的にstatusCodeとmessageを持つjsonを返すようになる
ArgumentsHostとは
実行されてるコンテキストの情報(RequestやResponse)を返すユーティリティクラス
code:ts:http-exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Request, Response } from 'express';
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception.getStatus();
response
.status(status)
.json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
});
}
}
code:ts:cats.controller.ts
@Post()
@UseFilters(new HttpExceptionFilter())
async create(@Body() createCatDto: CreateCatDto) {
throw new ForbiddenException();
}
// Controllerレベルでも適用可能
@UseFilters(new HttpExceptionFilter())
export class CatsController {}
Pipes
https://scrapbox.io/files/61cac1760da71d001d6d8e00.png
Pipeはcontrollerのハンドラが実行される前段階でRequestを変換したりvalidationをかましたりできるInjectableなレイヤー。
例えばParseIntPipeを使った下記の例ではリクエストのidが数値じゃない場合は例外になる。
code:ts:cats.controller.ts
@Get(':id')
async findOne(@Param('id', ParseIntPipe) id: number) {
return this.catsService.findOne(id);
}
DTOのfieldをRequestのbodyが満たしているかvalidationするカスタムpipe
code:ts:validation.pipe.ts
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer';
@Injectable()
export class ValidationPipe implements PipeTransform<any> {
async transform(value: any, { metatype }: ArgumentMetadata) {
if (!metatype || !this.toValidate(metatype)) {
return value;
}
const object = plainToClass(metatype, value);
const errors = await validate(object);
if (errors.length > 0) {
throw new BadRequestException('Validation failed');
}
return value;
}
private toValidate(metatype: Function): boolean {
return !types.includes(metatype);
}
}
code:ts:cats.controller.rs
@Post()
async create(
// ValidationPipeを指定するだけでbodyのバリデーションが完了になる
@Body(new ValidationPipe()) createCatDto: CreateCatDto,
) {
this.catsService.create(createCatDto);
}
Guards
ランタイムの前段階でリクエストのアクティベーションを判断するInjectableなレイヤー。Authorizationで主に使われる。
Authorizationの実装の例
Intercepters
ハンドラが実行される前後に処理を挟めるInjectableなレイヤー