🔥

NestJSのコア機能のまとめ

2024/03/09に公開

はじめに

NestJsについて、独学で学習したさいに、覚えておいたほうが良さそうなコア機能の基本をChatGPT4をつかってまとめたもの

モジュール

NestJSのモジュールは、アプリケーションの一部をカプセル化し、関連するコンポーネント(コントローラー、サービスなど)をグループ化するための機能です。モジュールは@Module()デコレータを使って定義され、アプリケーションの構造を整理し、機能の分離を容易にします。

モジュールの例

以下に、猫に関連する機能を扱うCatsModuleの例を示します。このモジュールはCatsControllerCatsServiceを含んでいます。

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

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {}

このモジュール定義では、以下のことが行われています:

  • controllers: このモジュールによって管理されるコントローラーを指定します。
  • providers: 依存性注入でこのモジュールによって使用されるサービスやプロバイダーを指定します。

モジュールの使用

CatsModuleは、アプリケーションのルートモジュール(通常はAppModule)にインポートされます。

app.module.ts
import { Module } from '@nestjs/common';
import { CatsModule } from './cats/cats.module';

@Module({
  imports: [CatsModule],
  // 他の設定...
})
export class AppModule {}

このルートモジュールでは、CatsModuleimports配列に追加することで、CatsModuleが提供する機能をアプリケーション全体で利用可能にします。

モジュールの利点

  • 機能の分離と再利用性: 各モジュールは、特定の機能に関連するコントローラー、サービス、その他のプロバイダーを一つにまとめることで、コードの再利用性を高め、アプリケーションを整理整頓します。
  • スコープ化されたプロバイダー: モジュールは、それに含まれるプロバイダー(サービスなど)のスコープを限定することができます。
  • 明確なアプリケーション構造: モジュールはアプリケーションの構造を明確にし、大規模なアプリケーションを管理しやすくします。

NestJSにおけるモジュールは、機能ごとにアプリケーションを整理する強力な方法を提供し、大規模で複雑なアプリケーションの開発を容易にします。

コントローラー

NestJSにおけるコントローラーは、特定のルート(エンドポイント)のリクエストを処理する役割を担います。コントローラーはデコレータを使用してルーティングのメタデータを指定し、関連するリクエストハンドリングロジックを実装します。

基本的なコントローラーの例

以下に、単純なNestJSコントローラーの例を示します。この例では、CatsControllerが定義され、いくつかの基本的なHTTPリクエストを処理します。

// cats.controller.ts
import { Controller, Get, Post, Body, Param, Delete, Put } from '@nestjs/common';
import { CatsService } from './cats.service';
import { CreateCatDto, UpdateCatDto } from './dto';

@Controller('cats')
export class CatsController {
  constructor(private catsService: CatsService) {}

  @Get()
  findAll() {
    return this.catsService.findAll();
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.catsService.findOne(id);
  }

  @Post()
  create(@Body() createCatDto: CreateCatDto) {
    return this.catsService.create(createCatDto);
  }

  @Put(':id')
  update(@Param('id') id: string, @Body() updateCatDto: UpdateCatDto) {
    return this.catsService.update(id, updateCatDto);
  }

  @Delete(':id')
  remove(@Param('id') id: string) {
    return this.catsService.remove(id);
  }
}

このコントローラーでは、以下のルートを定義しています:

  • GET /cats: すべての猫を取得します。
  • GET /cats/:id: 特定のIDを持つ猫を取得します。
  • POST /cats: 新しい猫を作成します。
  • PUT /cats/:id: 特定のIDを持つ猫のデータを更新します。
  • DELETE /cats/:id: 特定のIDを持つ猫を削除します。

各ルートハンドラは、CatsServiceを介してデータを取得、作成、更新、削除する処理を実装します。@Body()@Param()などのデコレータを使ってリクエストからデータを取得します。

DTOの使用

この例では、CreateCatDtoUpdateCatDtoを使用して、POSTとPUTリクエストのボディからデータを取得します。これはデータのバリデーションと型安全性を提供します。

dto/create-cat.dto.ts
export class CreateCatDto {
  name: string;
  age: number;
  breed: string;
}

dto/dto/update-cat.dto.ts
export class UpdateCatDto {
  name?: string;
  age?: number;
  breed?: string;
}

コントローラーは、NestJSアプリケーションにおけるルーティングとリクエストハンドリングの中心です。各メソッドは特定のHTTPリクエストに対応し、適切なビジネスロジック(この場合はCatsServiceを介した処理)を実行します。

サービス

NestJSのサービスは、ビジネスロジックやデータアクセス層の操作をカプセル化するために使用されます。サービスは、通常@Injectable()デコレータを使用して定義され、コントローラーや他のサービスから依存性注入によって利用されます。

サービスの例

以下は、猫に関連する操作を行うシンプルなCatsServiceの例です。このサービスは、猫のデータを管理するためのメソッドを提供します。

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): void {
    this.cats.push(cat);
  }

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

  findOne(id: string): Cat {
    return this.cats.find(cat => cat.id === id);
  }
}

このサービスでは、猫のデータを配列に保存しています。実際のアプリケーションでは、データベースや外部APIとの連携が含まれることが一般的です。

  • createメソッドは新しい猫を配列に追加します。
  • findAllメソッドは保存されているすべての猫を返します。
  • findOneメソッドは特定のIDを持つ猫を検索します。

サービスの使用

CatsServiceはコントローラー内で依存性注入により使用されます。以下は、CatsControllerCatsServiceを利用する例です。

cats.controller.ts
import { Controller, Get, Post, Body, Param } from '@nestjs/common';
import { CatsService } from './cats.service';
import { CreateCatDto } from './dto/create-cat.dto';

@Controller('cats')
export class CatsController {
  constructor(private catsService: CatsService) {}

  @Post()
  create(@Body() createCatDto: CreateCatDto) {
    this.catsService.create(createCatDto);
  }

  @Get()
  findAll() {
    return this.catsService.findAll();
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.catsService.findOne(id);
  }
}

このコントローラーでは、CatsServiceのメソッドが各エンドポイントのハンドラ内で使用されています。

注目点

  • 依存性注入: CatsService@Injectable()デコレータによってNestJSの依存性注入システムに登録されます。
  • 分離の原則: サービスはアプリケーションのビジネスロジックをコントローラーから分離し、コードの再利用性とテストの容易さを高めます。
  • テストの容易さ: サービスのメソッドは、独立してテスト可能です。これにより、ユニットテストの作成が容易になります。

サービスの使用はNestJSアプリケーションの設計において中心的な役割を果たし、機能のモジュール化と再利用性を提供します。

プロバイダーについて、

NestJSでは、「プロバイダー」は基本的な概念の一つで、依存性注入のシステムを通じて他のクラスにサービスや値を提供する役割を果たします。プロバイダーは通常、サービス、リポジトリ、ファクトリ、ヘルパーなどの形式をとり、@Injectable()デコレータを使って定義されます。

プロバイダーの例

以下に、簡単なサービスをプロバイダーとして実装する例を示します。このサービスは、猫に関連するデータを扱います。

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): void {
    this.cats.push(cat);
  }

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

  findOne(id: string): Cat {
    return this.cats.find(cat => cat.id === id);
  }
}

この例では、CatsService@Injectable()デコレータを使って定義されており、これによりNestJSの依存性注入システムによって他のクラス(例えばコントローラー)に注入できるようになります。

モジュールにおけるプロバイダーの登録

プロバイダー(この場合はCatsService)はモジュールに登録されます。

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

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {}

このモジュール定義では、CatsServiceproviders配列に含まれており、これによりCatsModule全体で利用可能になります。

プロバイダーの利点

  • 再利用性: ビジネスロジックやデータアクセスロジックを再利用可能な単位でカプセル化します。
  • テストの容易さ: 依存性注入を使用することで、単体テスト時にモックやスタブを簡単に注入できます。
  • 解耦合: システムの様々な部分を疎結合に保ち、変更や保守が容易になります。

プロバイダーはNestJSアプリケーションの構築において重要な要素であり、機能的で保守しやすいアプリケーションの設計に貢献します。

リゾルバ

NestJSでのリゾルバは、GraphQL APIにおけるクエリやミューテーションのリクエストを処理するために使用されます。リゾルバは、GraphQLのスキーマ定義に従って特定の操作を実行し、クライアントからのリクエストに応じてデータを取得または変更してレスポンスを返します。

リゾルバの例

以下に、猫に関連するデータを扱う簡単なCatsResolverの例を示します。このリゾルバは、猫のデータを取得、作成、更新するためのGraphQLクエリとミューテーションを提供します。

cats.resolver.ts
import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
import { CatsService } from './cats.service';
import { CatType } from './dto/cat.type';
import { CreateCatInput } from './inputs/create-cat.input';
import { UpdateCatInput } from './inputs/update-cat.input';

@Resolver(of => CatType)
export class CatsResolver {
  constructor(private catsService: CatsService) {}

  @Query(returns => [CatType])
  async getCats() {
    return this.catsService.findAll();
  }

  @Query(returns => CatType)
  async getCat(@Args('id') id: string) {
    return this.catsService.findOne(id);
  }

  @Mutation(returns => CatType)
  async createCat(@Args('input') createCatInput: CreateCatInput) {
    return this.catsService.create(createCatInput);
  }

  @Mutation(returns => CatType)
  async updateCat(@Args('id') id: string, @Args('input') updateCatInput: UpdateCatInput) {
    return this.catsService.update(id, updateCatInput);
  }
}

このリゾルバでは、以下の操作が定義されています:

  • getCats: すべての猫のデータを取得するためのクエリ。
  • getCat: 特定のIDを持つ猫のデータを取得するためのクエリ。
  • createCat: 新しい猫のデータを作成するためのミューテーション。
  • updateCat: 特定のIDを持つ猫のデータを更新するためのミューテーション。

GraphQLの型定義

リゾルバで使用されるCatTypeはGraphQLのObjectTypeであり、猫のデータの形式を定義します。CreateCatInputUpdateCatInputは、それぞれ新しい猫の作成と猫のデータの更新に使用される入力型です。

リゾルバの役割

  • データ取得と変更: クライアントからのGraphQLクエリやミューテーションに基づいて、データベースや他のデータソースからデータを取得または変更します。
  • ビジネスロジックの処理: データの取得や変更に際して、必要なビジネスロジックを実行します。
  • レスポンスの生成: クライアントが求めるデータの形式でレスポンスを生成し返します。

リゾルバは、NestJSにおけるGraphQL APIの中心的な部分であり、データ取得や変更のロジックをカプセル化し、APIのエンドポイントとして機能します。

DTOとTypesの型宣言について

ミドルウェア

NestJSにおけるミドルウェアは、ルーティングハンドラーが処理される前に特定の処理を行うために使用されます。これにより、リクエストやレスポンスを操作したり、特定のロジックを実行することができます。ミドルウェアは、通常、関数またはインジェクト可能なクラスとして実装されます。

ミドルウェアの例

以下に、簡単なログ記録のためのミドルウェアの例を示します。このミドルウェアは、リクエストの情報をコンソールにログ出力します。

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(`Request...`);
    console.log(`Method: ${req.method}`);
    console.log(`URL: ${req.url}`);
    next();
  }
}

このミドルウェアはuseメソッドを持ち、ExpressのRequestResponse、およびNextFunctionオブジェクトにアクセスできます。next()関数を呼び出すことで、次のミドルウェアまたはルーティングハンドラーに処理を渡します。

ミドルウェアの適用

ミドルウェアはモジュールレベルで適用されます。以下にAppModuleでのLoggerMiddlewareの適用方法を示します。

app.module.ts
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './logger.middleware';

@Module({
  // ...
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('*');  // すべてのルートにミドルウェアを適用
  }
}

configureメソッドを使用してLoggerMiddlewareをアプリケーションのすべてのルートに適用しています。

ミドルウェアの役割と利点

  • リクエスト処理の拡張: リクエストに対する前処理や、レスポンスに対する後処理を実行できます。
  • 共通機能の実装: 認証、ログ記録、リクエストデータの検証など、アプリケーション全体で共通の処理をミドルウェアにまとめることができます。
  • 柔軟な構成: 特定のルートやグローバルにミドルウェアを適用することができます。

NestJSのミドルウェアは、Expressミドルウェアによく似ており、アプリケーションのリクエスト処理パイプラインを強化します。

インターセプター

NestJSにおけるインターセプターは、メソッドの実行前後に追加的なロジックを挿入するための強力な機能です。インターセプターは、リクエスト処理の流れに介入し、データ変換、追加のロジックの実行、エラーハンドリングなどの機能を提供します。

インターセプターの例

以下に、レスポンスデータを変換するシンプルなインターセプターの例を示します。このインターセプターは、コントローラーのメソッドから返されるデータをラップして、常に特定の形式でレスポンスを返します。

response-transform.interceptor.ts
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

@Injectable()
export class ResponseTransformInterceptor<T> implements NestInterceptor<T, Response<T>> {
  intercept(context: ExecutionContext, next: CallHandler<T>): Observable<Response<T>> {
    return next.handle().pipe(
      map(data => ({
        data,
        timestamp: new Date().toISOString(),
      }))
    );
  }
}

interface Response<T> {
  data: T;
  timestamp: string;
}

このインターセプターは、interceptメソッドを持ち、リクエストを処理するハンドラー(CallHandler)にアクセスします。pipe()map()を使用してレスポンスデータを変換し、新しい形式で返します。

インターセプターの適用

インターセプターは、特定のルートハンドラーやコントローラー、またはグローバルに適用できます。以下は、コントローラーにインターセプターを適用する例です。

cats.controller.ts
import { Controller, UseInterceptors, Get } from '@nestjs/common';
import { CatsService } from './cats.service';
import { ResponseTransformInterceptor } from './response-transform.interceptor';

@Controller('cats')
@UseInterceptors(ResponseTransformInterceptor)
export class CatsController {
  constructor(private catsService: CatsService) {}

  @Get()
  findAll() {
    return this.catsService.findAll();
  }
}

インターセプターの役割

  • データ変換: レスポンスデータの形式を変更したり、特定の形式でラップしたりすることができます。
  • 追加のロジックの実行: メソッドの実行前後にログ記録、エラーハンドリング、トランザクション管理などの追加の処理を挿入できます。
  • レスポンス管理: レスポンスの形式やエラーの内容を一貫して管理できます。

NestJSのインターセプターは、アプリケーションの処理フローに柔軟に介入し、共通の機能を一箇所に集中させることができる強力なツールです。

パイプ

NestJSのパイプは、リクエスト処理の一部として、入力データの変換とバリデーションを行うために使用されます。パイプは、リクエストデータがコントローラのハンドラに渡される前に、そのデータを加工したり、特定の基準を満たしているか検証したりします。

パイプの例:バリデーションパイプ

バリデーションパイプは、入力データがDTO(Data Transfer Object)で定義された型と一致するかどうかを検証するためによく使用されます。以下に、class-validatorclass-transformerを使用したバリデーションパイプの例を示します。

まず、バリデーションを行うためのDTOを定義します:

create-cat.dto.ts
import { IsString, IsInt, Min, Max } from 'class-validator';

export class CreateCatDto {
  @IsString()
  readonly name: string;

  @IsInt()
  @Min(0)
  @Max(20)
  readonly age: number;
}

次に、このDTOを使用してバリデーションパイプを適用するコントローラのメソッドを定義します:

cats.controller.ts
import { Controller, Post, Body, UsePipes, ValidationPipe } from '@nestjs/common';
import { CreateCatDto } from './create-cat.dto';
import { CatsService } from './cats.service';

@Controller('cats')
export class CatsController {
  constructor(private catsService: CatsService) {}

  @Post()
  @UsePipes(new ValidationPipe())
  create(@Body() createCatDto: CreateCatDto) {
    return this.catsService.create(createCatDto);
  }
}

このコントローラでは、@UsePipes(new ValidationPipe())デコレータを使用してcreateメソッドにバリデーションパイプを適用しています。これにより、createCatDtoの各プロパティがclass-validatorデコレータによって定義された条件を満たすかどうかがチェックされます。

パイプの主な役割

  • データ変換: 入力データを所望の形式や型に変換します。例えば、文字列を数値に変換するなどです。
  • バリデーション: 入力データが特定の条件を満たしているかどうかを検証します。
  • 例外処理: データが無効な場合、適切なエラーレスポンスを返すことができます。

パイプはNestJSにおける強力なデータ加工ツールであり、APIのロバストネス(堅牢性)を向上させるのに役立ちます。

ガード

NestJSにおけるガードは、特定のルートへのアクセスを制御するために使用される機能です。主に認証や認可のために使われ、特定の条件が満たされているかどうかに基づいてリクエストの実行を許可するか決定します。ガードは@Injectable()デコレータを用いてサービスとして定義され、@Guard()デコレータを通じてルートハンドラに適用されます。

ガードの例: 認証ガード

以下に、簡単な認証ガードの例を示します。このガードは、リクエストに含まれる認証トークンを検証し、適切な認証情報が提供されていない場合にリクエストを拒否します。

auth.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest();
    return this.validateRequest(request);
  }

  private validateRequest(request: any): boolean {
    // 認証ロジックの実装(例: JWTトークンの検証)
    return true; // ここで認証が有効であればtrueを返す
  }
}

このガードはcanActivateメソッドを実装し、リクエストが特定の条件を満たしているかどうかを判断します。ここでは単純化のため常にtrueを返していますが、実際には認証トークンを検証するなどの処理を行うことが一般的です。

ガードの適用

ガードはコントローラーまたは特定のルートハンドラに適用されます。

cats.controller.ts
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from './auth.guard';

@Controller('cats')
@UseGuards(AuthGuard)
export class CatsController {
  @Get()
  findAll() {
    // 認証されたリクエストのみがこのメソッドにアクセス可能
  }
}

この例では、CatsControllerのすべてのルートにAuthGuardを適用しています。特定のメソッドにだけガードを適用することも可能です。

ガードの役割

  • アクセス制御: ガードは、特定の条件(通常は認証や認可)に基づいてルートへのアクセスを許可または拒否します。
  • ロジックの再利用: 認証や認可ロジックをガード内にカプセル化することで、アプリケーション全体で再利用できます。
  • セキュリティの向上: 不正なアクセスや不適切なリクエストを効率的にブロックし、アプリケーションのセキュリティを強化します。

NestJSのガードは、アプリケーションのセキュリティに重要な役割を果たし、特定のルートへのアクセスをきめ細かく管理することを可能にします。

フィルター

NestJSにおけるフィルターは、アプリケーションで発生した例外をキャッチし、クライアントへのレスポンスをカスタマイズするために使用されます。フィルターを使うことで、標準的な例外レスポンスをオーバーライドし、より詳細なエラー情報を提供したり、特定のフォーマットに合わせてエラーを返したりできます。

フィルターの例: カスタム例外フィルター

以下に、カスタム例外フィルターの基本的な実装を示します。このフィルターは、発生した例外をキャッチし、カスタマイズされたレスポンスを返します。

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();
    const exceptionResponse = exception.getResponse();

    response
      .status(status)
      .json({
        statusCode: status,
        timestamp: new Date().toISOString(),
        path: request.url,
        message: exceptionResponse['message'], // またはカスタムメッセージ
      });
  }
}

このフィルターは@Catch(HttpException)デコレータを使ってHttpException(およびそのサブクラス)をキャッチし、catchメソッドでカスタムのレスポンスを生成します。

フィルターの適用

フィルターは、特定のルート、コントローラ、またはグローバルに適用することができます。以下はコントローラーレベルでフィルターを適用する例です。

cats.controller.ts
import { Controller, UseFilters } from '@nestjs/common';
import { HttpExceptionFilter } from './http-exception.filter';

@Controller('cats')
@UseFilters(new HttpExceptionFilter())
export class CatsController {
  // コントローラのメソッド...
}

このコントローラでは、すべてのルートでHttpExceptionFilterが使用されます。

フィルターの役割

  • エラーハンドリング: アプリケーションで発生した例外を捕捉し、処理します。
  • レスポンスのカスタマイズ: 標準的なエラーレスポンスに代わって、カスタマイズされたレスポンスを返すことができます。
  • 例外の一元管理: 例外処理ロジックをフィルターに集中させることで、コードの再利用性と整理が向上します。

NestJSの例外フィルターは、エラーハンドリングのプロセスをカスタマイズし、アプリケーションのエラーレスポンスを一貫性のあるものにするのに役立ちます。

ルーティング

ルーティングとは、アプリケーションが受け取った特定のHTTPリクエスト(URLとHTTPメソッドの組み合わせ)を適切なハンドラー(通常はコントローラ内のメソッド)にマッピングするプロセスです。

  • : @Get('cats')デコレータは、GET /cats というHTTPリクエストを対応するメソッドにマッピングします。

リクエストハンドリング

リクエストハンドリングとは、ルーティングによって決定された特定のリクエストに対する処理を実行することです。コントローラのメソッドは、リクエストを受け取り、必要なビジネスロジックを実行した後、レスポンスを返します。

  • : findAll()メソッドはGET /catsリクエストを処理し、猫のデータのリストを返すかもしれません。

デコレータ

デコレータは、クラス、メソッド、プロパティ、パラメータにメタデータを追加するための宣言的な構文です。NestJSでは、ルーティング、依存性注入、パラメータバリデーションなどにデコレータを広く使用します。

  • : @Injectable(), @Controller(), @Get(), @Post(), @Body(), @Param() など。

メタデータ

メタデータとは、データに関する追加情報のことで、NestJSでは主にデコレータを通じてクラスやメソッドに付与されます。これにより、NestJSの実行時システムは、各構成要素がどのように動作すべきかを理解します。

  • : @Get('cats')デコレータは、特定のメソッドがGET /catsリクエストに応答するべきであることを示すメタデータを提供します。

これらの概念は、NestJSアプリケーションの構造と動作の根幹をなしており、フレームワークの利便性と強力な機能を提供するための基礎となっています。

Discussion