NestJsの基本
NestJSのアーキテクチャとDIの方法をまとめる。
どうでもいいけどNestJSのロゴと「旬」の字は似ている。「勺」もそこそこ。
セットアップ
次のコマンドを実行。
$ npm i -g @nestjs/cli
$ nest new <プロジェクト名>
nest new <プロジェクト名>
した後に、使用するパッケージマネージャーを聞かれたらyarnを選択する(npm/yarn/pnpmから好みで)
プロジェクトが作成されると、srcフォルダ以下が次のようになる。
src
┣app.controller.spec.ts // ユニットテスト用
┣app.controller.ts // コントローラ。パスに対するリクエスト→コントローラの対応づけ
┣app.module.ts // アプリケーション全体のフィーチャーモジュールを束ねるボス
┣app.service.ts // ビジネスロジック担当
┗main.ts // アプリケーションのエントリポイント
[余談]tsconfig.jsonのcompilerOptions
"strict": true
と"noImplicitAny": true
に設定しておいた方がいい
{
"compilerOptions": {
略
"noImplicitAny": true
略
"strict": true
}
}
この時点でnode_modulesがなかったらyarn
を実行してパッケージインストール。
NestJSの基本3要素
- Controller
- Service
- Module
基本的には、これら3つを単位として1機能ができるイメージ
e.g. ユーザ関連の機能では、
UserController, UserService, UserModule
全体像
※featureモジュールを作成後は、ルートモジュールに登録すること。
例えばUserModuleを作成した場合は、次のようにルートモジュールへ登録する。
// app.module.ts
import { Module } from '@nestjs/common';
import { UserModule } from './user/user.module';
@Module({
imports: [UserModule], // ここ
})
export class AppModule {}
※cliでモジュール作成$ nest g mo user
した場合は自動でルートモジュールへ登録される。
Module
Moduleは、コントローラーやサービスをまとめ、アプリケーション内で利用できるようにNestJSに登録する役割を持つ。
cliで次のようにして作成可能("cats"という "mo"duleを "g"enerateする)
$ nest g mo cats
次のコードはfeatureモジュールを想定した説明用サンプル。
@Module()
デコレータをつけることにより、そのクラスをモジュールとして定義できる。
この@Module()
デコレータ内にオブジェクト形式でプロパティを記述することで、コントローラーやサービスを登録する。
// cats.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
import { PrismaModule } from '.......';
import { PrismaService } from '.......';
import { JwtStrategy } from '.......';
import { JwtAuthGuard } from '.......';
@Module({
imports: [PrismaModule], // 他のモジュールの機能を利用する
controllers: [CatsController],
providers: [CatsService], // DIして使う
exports: [JwtStrategy, JwtAuthGuard, PrismaService], // 他のモジュールでもimport可能になる
})
export class CatsModule {}
🔍@Moduleデコレータ内のプロパティを簡単に説明
imports: []
:そのモジュール内で使用したい外部モジュールを追加する。データベースモジュールや認証モジュールなど。
controllers: []
:@Controller
デコレータが付与されたクラスを追加する。
providers: []
:@Injectable
デコレータが付与されたクラス(多くはServiceクラス)を追加することで、DIを可能とする。
exports: []
:他のモジュールでも広く利用するような機能を追加する。
くどいけどfeatureモジュールを作ったなら、 ルートモジュールへの登録を忘れずに。
// app.module.ts
@Module({
imports: [CatsModule],
})
Controller
Controllerは、クライアントからのリクエストを受けてクライアントにレスポンスを返す。ルーティング処理担当。
cliでは次のようにして作成可能("products"という "co"ntrollerを "g"enerateする)
$ nest g co products
次のコードは、featureモジュールを想定した説明用サンプル。
@Controller()
デコレータをつけることで、そのクラスをコントローラとして定義できる。
また、この@Controller()
デコレータの引数に('パス名')を記述することで、エンドポイントのパスを指定できる。下のソースは /products への各種リクエストということになる。
@Controller('products')
export class ProductsController {
constructor(private readonly productsService: ProductsService) {}
// GET /products
@Get()
async findAll(): Promise<Products[]> {
return await this.productsService.findAll();
}
// GET /products/81ba48e7-54a1-4bb3-9122-14e79471f174
@Get(':id')
async findById(@Param('id', ParseUUIDPipe) id: string): Promise<Products> {
return await this.productsService.findById(id);
}
// POST /products
@Post()
// 略
// PATCH /products/81ba48e7-54a1-4bb3-9122-14e79471f174
@Patch(':id')
// 略
// DELETE /products/81ba48e7-54a1-4bb3-9122-14e79471f174
@Delete(':id')
// 略
}
/products からさらにパス階層を掘りたい場合は、各HTTPメソッドハンドラのデコレータにも引数を渡せば可能。→@Get(':id')
が参考になる。
メソッドハンドラの中身では、Serviceクラスのメソッドを呼んでビジネスロジックを実行させる。
Service
ビジネスロジックを定義する。
@Injectable
デコレータをつけることで、そのクラスをDI用のクラスとして定義できる。
@Injectable()
export class ProductsService {
constructor(private readonly productRepository: ProductRepository) {}
async findAll(): Promise<Product[]> {
return await this.productRepository.find();
}
async findById(id: string): Promise<Product> {
const foundItem = await this.productRepository.findOne(id);
if (!foundItem) {
throw new NotFoundException();
}
return foundProduct;
}
}
次にこの@Injectable
デコレータがついたクラスをModuleのprovidersに追加することで、DIの準備が整う。
@Module({
controllers: [ProductsController],
providers: [ProductsService]
})
最後にControllerクラスのconstructorの引数にDI用のServiceクラスを渡し、construction DIを行う。
@Injectable()
export class ProductsController {
constructor(private readonly productsService: ProductsService) {}
…
}
NestJSにおけるDIとは...
以下はプロジェクト作成時に自動生成されたAppControllerがAppServiceのgetHelloメソッドを呼び出している例
特徴として、
- Controller内では、AppServiceをインスタンス化していない。
- 代わりに、Controllerがインスタンス化される際、constructorに記述されたAppService(appService)も自動的にインスタンス化される。これはNestJSのランタイムシステムが内包している(Inversion of Controlコンテナという 仕組み? 手法?)にやらせることで実現できているらしいがよくわからん。
上記を経て、AppControllerにAppServiceのgetHelloメソッドがInjectionされる。
以上