Skip to content

ADR-006: NestJS採用(バックエンドフレームワーク)

ステータス

採用済み

意思決定者

  • 太田裕貴(CTO)
  • バックエンドチーム

決定日

2021年頃(プロジェクト初期)


背景(Context)

Qudenのバックエンドは、動画メタデータ管理、ユーザー認証、動画アップロード、検索機能など、多様なAPIを提供する必要があります。

現状の課題

  • Expressだけでは、大規模プロジェクトの構造化が困難
  • 依存性注入(DI)の仕組みがなく、テスタビリティが低い
  • APIのバリデーション、エラーハンドリングが標準化されていない
  • TypeScriptとの統合が不十分
  • マイクロサービスへの拡張性が考慮されていない

解決したいこと

  • 大規模プロジェクトに耐える構造化されたアーキテクチャ
  • 依存性注入(DI)によるテスタビリティ向上
  • TypeScriptファーストなフレームワーク
  • バリデーション、認証、エラーハンドリングの標準化
  • 将来的なマイクロサービス化への対応

要件(Requirements)

機能要件

  • RESTful APIの実装
  • リクエストバリデーション
  • 認証・認可(JWT、AWS Cognito統合)
  • エラーハンドリングの統一
  • データベース統合(MongoDB)
  • ファイルアップロード(S3統合)

非機能要件

  • 開発効率: 生産性の高いフレームワーク
  • 保守性: 明確なアーキテクチャと構造化
  • テスタビリティ: ユニットテスト・統合テストが容易
  • スケーラビリティ: 将来的なマイクロサービス化に対応

制約条件

  • 技術的制約: TypeScript、MongoDBと統合可能
  • ビジネス的制約: オープンソースで無料
  • 時間的制約: 学習コストが許容範囲内

検討した選択肢

選択肢1: Express(軽量フレームワーク)

概要

Node.jsの最も一般的なWebフレームワーク。軽量でシンプル。

メリット

  • 学習コストが低い(チームメンバーが既に習得)
  • 軽量で柔軟性が高い
  • エコシステムが成熟している
  • ミドルウェアが豊富

デメリット

  • アーキテクチャの標準化がない(自由度が高すぎる)
  • 依存性注入(DI)の仕組みがない
  • TypeScriptとの統合が不十分
  • バリデーション、認証等を自前で実装する必要がある
  • 大規模プロジェクトでの構造化が困難

実装コスト

  • 初期実装: 低
  • 学習コスト: 低
  • 保守コスト: 高(構造化が困難)

選択肢2: Fastify(高速軽量フレームワーク)

概要

Express代替として注目される、高速で軽量なフレームワーク。

メリット

  • Expressより高速(ベンチマーク上)
  • スキーマベースのバリデーション(JSON Schema)
  • TypeScriptサポート

デメリット

  • エコシステムがExpressより小さい
  • アーキテクチャの標準化がない
  • 依存性注入(DI)の仕組みがない
  • 学習リソースが少ない

実装コスト

  • 初期実装: 中
  • 学習コスト: 中
  • 保守コスト: 中

選択肢3: NestJS(エンタープライズフレームワーク)

概要

Angular風のアーキテクチャを採用した、TypeScriptファーストのNode.jsフレームワーク。

メリット

  • TypeScriptファースト: デフォルトでTypeScript、型安全性が高い
  • 依存性注入(DI): テスタビリティが高い
  • モジュラーアーキテクチャ: 大規模プロジェクトに最適
  • 標準化: バリデーション(class-validator)、認証(Passport)、エラーハンドリング等が標準化
  • Mongoose統合: MongoDBとの統合が容易
  • マイクロサービス対応: @nestjs/microservices でマイクロサービス化が容易
  • CLI: ボイラープレートを自動生成
  • 豊富なドキュメント: 公式ドキュメントが充実

デメリット

  • 学習コストが高い(Angular風のアーキテクチャ)
  • Expressより複雑
  • ボイラープレートが多い

実装コスト

  • 初期実装: 中
  • 学習コスト: 中〜高
  • 保守コスト: 低(構造化されている)

決定(Decision)

選択した技術: NestJS

選択理由

  1. 要件との適合性

    • TypeScriptファーストで、型安全性が高い
    • 依存性注入(DI)により、ユニットテストが容易
    • class-validatorによるリクエストバリデーションが標準化
    • Passportによる認証・認可が容易(AWS Cognito統合も可能)
    • Mongooseとの統合が公式サポートされている
  2. 技術的妥当性

    • モジュラーアーキテクチャ: Modules、Controllers、Servicesで明確に分離
    • スケーラビリティ: @nestjs/microservices でマイクロサービス化が容易
    • テスタビリティ: DIにより、モックが容易でテストが書きやすい
    • エコシステム: @nestjs/* パッケージが充実(GraphQL、WebSocket、CQRS等)
  3. コスト対効果

    • 学習コストは高いが、長期的な保守性が大幅に向上
    • 公式ドキュメント・学習リソースが充実
    • CLIによるボイラープレート生成で開発効率が高い
  4. 将来性

    • エンタープライズ向けNode.jsフレームワークとして広く採用
    • 積極的な開発とコミュニティサポート

実装方針

  1. モジュラーアーキテクチャの採用

    • 機能ごとにモジュールを分離(UsersModule、ItemsModule、AuthModule等)
    • Controllers(HTTP)、Services(ビジネスロジック)、Repositories(データアクセス)で責務を分離
  2. 依存性注入(DI)の活用

    • Services、Repositories等をDIコンテナで管理
    • テスト時にモックを注入しやすい設計
  3. バリデーションの標準化

    • class-validator + class-transformer でDTOを定義
    • ValidationPipe でリクエストバリデーションを自動化
  4. 認証・認可

    • PassportStrategy でAWS Cognito JWTを検証
    • Guards でエンドポイントごとに認証・認可を制御
  5. エラーハンドリング

    • Exception Filters でエラーレスポンスを統一
    • カスタム例外クラス(NotFoundException、BadRequestException等)

受け入れたトレードオフ

トレードオフ1: 学習コスト

  • 影響: Angular風のアーキテクチャに慣れる必要がある
  • 受け入れ理由: 公式ドキュメント・学習リソースが充実しており、長期的なメリットが大きい
  • 軽減策: チーム内での勉強会、公式ドキュメントの共有、ペアプログラミング

トレードオフ2: ボイラープレートの多さ

  • 影響: ExpressよりもコードBuoyancy必要
  • 受け入れ理由: NestJS CLIによりボイラープレートを自動生成可能、構造化されたコードは保守性が高い
  • 軽減策: Nest CLI の積極的な活用(nest g module, nest g service等)

トレードオフ3: パフォーマンスオーバーヘッド

  • 影響: DIコンテナ等のオーバーヘッドがある
  • 受け入れ理由: ほとんどの場合、パフォーマンスボトルネックはDBやネットワークI/Oであり、フレームワークのオーバーヘッドは微小
  • 軽減策: 必要に応じてキャッシング、最適化を実施

結果(振り返り)

うまくいったこと(Pros)

  • 構造化されたコード: モジュラーアーキテクチャにより、コードが整理され保守性が大幅に向上
  • テスタビリティの向上: DIにより、ユニットテストが容易になった
  • 開発効率: class-validator、Passport等の標準化により、開発速度が向上
  • TypeScript統合: TypeScriptファーストで、型安全性が確保された
  • チーム開発: 明確なアーキテクチャにより、チームメンバー間での一貫性が確保された

うまくいかなかったこと(Cons)

  • 学習曲線: 新規メンバーがNestJSのアーキテクチャに慣れるのに時間がかかった
  • ボイラープレート: 小規模な機能でもModule、Controller、Service等を作成する必要があり、面倒に感じることがあった

学び

  • モジュール分離の重要性: 適切なモジュール分離により、コードの見通しが良くなった
  • DI の強力さ: テスト時のモック注入が容易で、テスト駆動開発(TDD)がしやすくなった
  • class-validatorの活用: DTOでバリデーションを定義することで、APIの型安全性が向上

今後の改善点

  • GraphQLの導入検討: @nestjs/graphql を活用したGraphQL APIの検討
  • CQRS パターンの導入: 複雑なビジネスロジックにCQRSパターンを適用
  • マイクロサービス化: 将来的に@nestjs/microservices を活用したマイクロサービス化

参考リンク


関連ドキュメント