NestJSアプリケーションは、Module、Controller、Providerという3つの主要なビルディングブロックで構成されています。これらの要素を正しく理解し活用することで、保守性が高く、テストしやすいアプリケーション構造を設計できます。本記事では、各要素の役割と依存性注入(DI)の仕組み、そして実践的なモジュール分割パターンを解説します。
実行環境と前提条件
本記事の内容を実践するにあたり、以下の環境を前提としています。
| 項目 | バージョン・要件 |
|---|---|
| Node.js | 20以上 |
| npm | 10以上 |
| NestJS | 11.x |
| OS | Windows / macOS / Linux |
| エディタ | VS Code(推奨) |
事前に以下の準備を完了してください。
- NestJS CLIのインストール済み
- NestJSプロジェクトの作成済み(
nest newコマンドで作成可能)
プロジェクトの作成方法はNestJS入門記事を参照してください。
NestJSアーキテクチャの全体像
NestJSアプリケーションの構造を理解するために、まず3つの主要コンポーネントの関係を確認しましょう。
graph TD
A[Module] --> B[Controller]
A --> C[Provider/Service]
B --> C
D[Client Request] --> B
B --> E[Response]
C --> F[Database/External API]各コンポーネントの役割は以下のとおりです。
| コンポーネント | 役割 | デコレータ |
|---|---|---|
| Module | アプリケーションの構造を定義し、関連するController・Providerをグループ化する | @Module() |
| Controller | HTTPリクエストを受け取り、レスポンスを返す | @Controller() |
| Provider | ビジネスロジックを実装し、Controllerに注入される | @Injectable() |
Providerとは - ビジネスロジックの実装単位
Providerは、NestJSにおけるビジネスロジックの実装単位です。Service、Repository、Factory、Helperなど、様々な形態のクラスがProviderとして機能します。
@Injectableデコレータの役割
@Injectable()デコレータを付与することで、そのクラスはNestのIoC(Inversion of Control)コンテナによって管理されます。これにより、依存性注入の対象となり、他のクラスに自動的に注入できるようになります。
|
|
このServiceは、ユーザーデータのCRUD操作を担当するビジネスロジックを実装しています。@Injectable()デコレータにより、NestのDIシステムで管理され、必要な場所に自動的に注入されます。
Providerの種類と使い分け
NestJSでは、様々な種類のProviderを定義できます。
| Providerの種類 | 用途 | 例 |
|---|---|---|
| Service | ビジネスロジックの実装 | UsersService、AuthService |
| Repository | データアクセス層 | UsersRepository |
| Factory | オブジェクトの生成 | DatabaseConnectionFactory |
| Helper | ユーティリティ関数群 | HashHelper、DateHelper |
Controllerとは - リクエストハンドリングの窓口
Controllerは、クライアントからのHTTPリクエストを受け取り、適切なレスポンスを返す責務を持ちます。ビジネスロジックは直接実装せず、Providerに委譲することが重要です。
@Controllerデコレータの使い方
@Controller()デコレータは、クラスをコントローラーとして定義します。引数にはルートパスのプレフィックスを指定できます。
|
|
ControllerとServiceの責務分離
このコードでは、責務が明確に分離されています。
flowchart LR
A[HTTP Request] --> B[Controller]
B --> C{リクエスト処理}
C --> D[パラメータ抽出]
C --> E[バリデーション]
C --> F[Service呼び出し]
F --> G[Service]
G --> H[ビジネスロジック]
H --> I[データ操作]
I --> J[結果返却]
J --> B
B --> K[HTTP Response]Controllerの責務は以下に限定されています。
- HTTPリクエストの受信とレスポンスの送信
- リクエストパラメータの抽出(
@Body()、@Param()など) - Serviceへの処理委譲
- エラーハンドリング(HTTPステータスコードの決定)
ビジネスロジック(データの作成・検索・更新・削除)はすべてServiceに委譲しています。
Moduleとは - アプリケーションの構造化単位
Moduleは、関連するController、Provider、その他のモジュールをグループ化し、アプリケーションの構造を定義します。すべてのNestJSアプリケーションは、少なくとも1つのルートモジュールを持ちます。
@Moduleデコレータのプロパティ
@Module()デコレータは、以下のプロパティを持つオブジェクトを受け取ります。
| プロパティ | 説明 |
|---|---|
imports |
このモジュールで必要な他のモジュールをインポートする |
controllers |
このモジュールで定義するControllerを登録する |
providers |
このモジュールで定義するProviderを登録する |
exports |
他のモジュールに公開するProviderを指定する |
Feature Moduleの作成
ユーザー管理機能をカプセル化したUsersModuleを作成します。
|
|
このモジュールは、ユーザー関連のController、Serviceをカプセル化しています。exports配列にUsersServiceを追加することで、他のモジュールからこのServiceを利用できるようになります。
ルートモジュールへの統合
Feature Moduleはルートモジュール(通常はAppModule)にインポートして使用します。
|
|
依存性注入(DI)の仕組み
依存性注入は、NestJSの根幹をなす設計パターンです。クラスが必要とする依存関係を外部から注入することで、疎結合でテストしやすいコードを実現します。
コンストラクタ注入
NestJSでは、コンストラクタを通じて依存関係を注入します。TypeScriptの型情報を利用して、適切なインスタンスが自動的に解決されます。
|
|
この簡潔な構文は、以下のコードと同等です。
|
|
DIコンテナの動作フロー
NestJSのDIコンテナは、以下のフローで依存関係を解決します。
sequenceDiagram
participant App as Application
participant Container as IoC Container
participant Module as Module
participant Provider as Provider
App->>Container: アプリケーション起動
Container->>Module: モジュールスキャン
Module->>Container: Provider登録情報
Container->>Provider: インスタンス生成
Container->>Container: 依存関係グラフ構築
Container->>Provider: 依存関係注入
Note over Container: すべての依存関係が解決済み
App->>Container: リクエスト処理開始Provider間の依存関係
Providerは他のProviderに依存できます。例えば、OrdersServiceがUsersServiceに依存する場合を考えます。
|
|
この依存関係を解決するには、OrdersModuleでUsersModuleをインポートする必要があります。
|
|
モジュール設計のベストプラクティス
大規模アプリケーションでは、適切なモジュール設計が重要です。
機能単位でのモジュール分割
ドメイン(機能領域)ごとにモジュールを分割することで、コードの凝集度が高まり、保守性が向上します。
src/
├── app.module.ts # ルートモジュール
├── users/
│ ├── users.module.ts
│ ├── users.controller.ts
│ ├── users.service.ts
│ └── dto/
│ ├── create-user.dto.ts
│ └── update-user.dto.ts
├── orders/
│ ├── orders.module.ts
│ ├── orders.controller.ts
│ ├── orders.service.ts
│ └── dto/
│ └── create-order.dto.ts
├── products/
│ ├── products.module.ts
│ ├── products.controller.ts
│ └── products.service.ts
└── common/
├── common.module.ts
└── services/
└── logger.service.ts
共有モジュールの作成
複数のモジュールで共通して使用するProviderは、共有モジュールとして切り出します。
|
|
@Global()デコレータを使用すると、このモジュールを明示的にインポートしなくても、アプリケーション全体でProviderを使用できます。ただし、グローバルモジュールの乱用はアプリケーションの構造を不明確にするため、必要な場合にのみ使用してください。
モジュール間の依存関係
モジュール間の依存関係は、循環依存を避けるよう設計します。
graph TD
A[AppModule] --> B[UsersModule]
A --> C[OrdersModule]
A --> D[ProductsModule]
A --> E[CommonModule]
C --> B
C --> D
B -.-> E
C -.-> E
D -.-> E
style E fill:#f9f,stroke:#333上図では、CommonModuleがグローバルモジュール(点線)として全モジュールから利用可能であり、OrdersModuleがUsersModuleとProductsModuleに依存しています。
実践:完全なモジュール構成例
学んだ内容を統合した完全なモジュール構成を確認します。
プロジェクト構造
src/
├── main.ts
├── app.module.ts
├── common/
│ ├── common.module.ts
│ └── services/
│ └── logger.service.ts
├── users/
│ ├── users.module.ts
│ ├── users.controller.ts
│ ├── users.service.ts
│ └── interfaces/
│ └── user.interface.ts
└── orders/
├── orders.module.ts
├── orders.controller.ts
├── orders.service.ts
└── interfaces/
└── order.interface.ts
各ファイルの実装
LoggerServiceの実装例を示します。
|
|
ルートモジュールの構成例を示します。
|
|
動作確認
アプリケーションを起動して動作を確認します。
|
|
以下のエンドポイントでAPIをテストできます。
|
|
期待される結果として、ユーザー作成時にはIDが自動付与されたユーザーオブジェクトが返却され、一覧取得時には登録済みの全ユーザーが配列で返却されます。
まとめ
本記事では、NestJSアプリケーションの3つの柱であるModule、Controller、Providerについて解説しました。
学んだ内容を整理します。
- Provider:
@Injectable()デコレータでマークされ、ビジネスロジックを実装するクラス - Controller:
@Controller()デコレータでマークされ、HTTPリクエストを処理するクラス - Module:
@Module()デコレータでマークされ、関連するController・Providerをグループ化するクラス - 依存性注入:コンストラクタを通じて依存関係を自動的に解決する仕組み
- モジュール設計:機能単位での分割と共有モジュールの活用が重要
これらの概念を正しく理解し活用することで、保守性が高く、テストしやすいNestJSアプリケーションを構築できます。次のステップとして、HTTPメソッドデコレータを活用したREST APIの実装や、Pipe、Guard、Interceptorなどのミドルウェア機能について学習を進めてください。