Nest.jsの基礎を学ぶ
開発環境
- Nest 9.0.0
前提
本記事ではNestJSに入門したばかり、もしくはこれから学びたい!と思っている方を対象としています。
本題
お仕事で使う可能性が出てきたので最近Nestを学び始めました。
公式ドキュメントやUdemyなど活用しながら簡単なCRUDアプリケーションは作れるかなという感覚はありますが、いまいち自分の中で全体像がぼやっとしてしまっているなと感じるので、改めて全体像とコアとなる要素であるControllers, Services, Modulesについて整理していきます。
全体像
まず、アプリケーションのエントリーポイントはmain.tsになります。
そして、ルートモジュール(=app.module.ts)に登録されたモジュールがアプリケーションで利用されることになります。
ということはルートモジュールで読み込みを行わなかった場合、それらは利用されないことになります。
アプリケーション開発では様々な機能を実装していくことになりますが、それらを機能ごとに分割するのが通常です。よってルートモジュールに読み込ませる機能モジュール(=feature.module.ts ※ feature部分は機能によって名称が変わります)が複数できることになります。
そして、機能モジュールごとにController、Serviceを実装していく流れです。
Controllers
【役割】
クライアントからのリクエストを受け付け、クライアントにレスポンスを返すルーティングの機能を担う。
【定義】
- classに@Controller()デコレータをつける
- メソッドにHTTPメソッドデコレータをつける
【実装例】
import { Controller, Get, Req } from '@nestjs/common';
import { ItemsService } from './items.service';
import { Item } from '@prisma/client';
@Controller('items')
export class ItemsController {
constructor(private readonly itemsService: ItemsService) {}
@Get()
getItems(@Req() req: Request): Promise<Item[]> {
return this.itemService.getItems(req.user.id);
}
実装例では、/itemsのパスに対してGETメソッドでリクエストを送るとgetItemsが実行されることになります。
引数にServiceを受け取り、Serviceで定義された関数を実行するという流れです。
Services
【役割】
ビジネスロジックを定義する。
処理の流れとしてはControllerから呼び出される。
※Controllerにビジネスロジックを実装することも可能ですが、責務を分離させることが保守性や拡張性向上につながるのでロジックはServicesで定義する
【定義】
- classに@Injectable()デコレータをつける
- Controllerで利用するビジネスロジックを実装する
【実装例】
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { Item } from '@prisma/client';
@Injectable()
export class ItemService {
constructor(private prisma: PrismaService) {}
getItems(userId: number): Promise<Item[]> {
return this.prisma.item.findMany({
where: {
userId,
},
orderBy: {
createdAt: 'desc',
},
});
}
Modules
【役割】
関連するController、Serviceなどをまとめて、アプリケーションとして利用できるようにNestJSに登録する。
【定義】
- classに@Module()デコレータをつける
- @Module()デコレータのプロパティを記述する
【実装例】
import { Module } from '@nestjs/common';
import { ItemsModule } from './items/items.module';
@Module({
imports: [ItemsModule],
controllers: [],
providers: [],
})
export class AppModule {}
import { Module } from '@nestjs/common';
@Module({
imports: [AuthModule],
controllers: [ItemsController],
providers: [ItemsService],
})
export class ItemsModule {}
それぞれのプロパティに追加するクラス、モジュールは以下です。
- providers: @Injectableデコレータがついたクラス
- controllers: @Controllerデコレータがついたクラス
- imports: モジュール内部で必要な外部モジュール
- exports: 外部のモジュールにエクスポートしたいServiceなど
その他重要な概念
【Dependency Injection (DI)】
ソフトウェアを疎結合にするためのデザインパターンの1つです。
日本語で「依存性の注入」などと呼ばれます。
NestJSでは外部から依存関係のあるオブジェクトを注入することを指します。
依存関係とは、例えばUsersController、UsersServiceがあるとき、UsersControllerはUsersServiceがないと自身でロジックを持っていないので動作しません。このような関係性を依存していると言います。
DIすることで、オブジェクトの生成(Service)と利用(Cotroller)の責務を分離することができます。
そして、NestJSではこのDIを簡単な設定でできる仕組みが用意されています。
この仕組みは「DIコンテナ」と呼ばれます。
【実装例】
- ModuleのprovidersにServiceを登録する
@Module({
imports: [ItemsModule],
controllers: [],
providers: [ItemsService],
})
- ControllerのconstructorでServiceを引数に取る
@Controller('items')
export class ItemsController {
constructor(private readonly itemService: ItemService) {}
@Get(':id')
find(@Param('id') id: number) {
this.itemService.find(id)
}
}
まとめ
アプリケーションを実装する上で最低限の構成3つを紹介しました。
もちろん様々なユースケースに対応していく上では3つ以外のMiddleware、Guardsなどが必要になります。
しかしなにごとも基礎から。
まずはこの記事の内容を抑えつつ徐々にできることを増やしていきたいです!