💁‍♂️

NestJS初めてみたよ(初心者の初心者による初心者のための記事)

2022/11/03に公開

背景

NestJS使ってみたいけど、全然わからんやん→ヨシ!調べるかというモチベーション
自分の勉強用にまとめてみます。
車輪の再生産です。はい

NestJSとは

Nest (NestJS) は、効率的でスケーラブルな Node.js サーバー側アプリケーションを構築するためのフレームワークです。 プログレッシブ JavaScript を使用し、TypeScript を使用して構築され、完全にサポートされ (開発者は純粋な JavaScript でコーディングできます)、OOP (オブジェクト指向プログラミング)、FP (関数型プログラミング)、および FRP (関数型リアクティブ プログラミング) の要素を組み合わせます。 内部では、Nest は Express (デフォルト) のような堅牢な HTTP サーバー フレームワークを利用し、オプションで Fastify を使用するように構成することもできます! Nest は、これらの一般的な Node.js フレームワーク (Express/Fastify) より上のレベルの抽象化を提供しますが、それらの API を開発者に直接公開します。 これにより、開発者は、基盤となるプラットフォームで利用可能な無数のサードパーティ モジュールを自由に使用できます。
https://docs.nestjs.com/

NestJSのドキュメントの日本語訳 素晴らしい記事ありがとうございます(現在NestJSはver9なのであくまで参考程度に 2022/11/3)
https://zenn.dev/kisihara_c/books/nest-officialdoc-jp

要するに、Nodejsでサーバーサイドアプリケーションを作れるフレームワークということですね。
Node.js使ってた人からするとExpressをアーキテクチャしっかり考えられていい感じに作れるよってコト?
標準では

  • 言語はTypeScript
  • testにはjestを使用
  • アーキテクチャはAngularから影響を受けているんだとかないとか、、、

実装

How to use

  1. まずは、nodeとnpmをダウンロードしてください。(バージョンの指定はプロジェクトによって違います。)

  2. その後Nest CLIをインストールしてnew projectを作ります。

    $ npm i -g @nestjs/cli
    $ nest new project-name
    
  3. プロジェクト作成途中でパッケージマネジャーをどうするか聞かれますので、任意のパッケージマネージャーを選択してください(大体npm or yarnですよね)

  4. ローカルでサーバーを起動してください。

    $ cd project-name
    $ npm run start
    
  5. ブラウザでlocalhost:3000にアクセスしてHello Worldが確認できたらOKですね。ひとまずNest.jsで開発ができる準備が整いました。

確認

なんとなく動いたけどどういうこと??

動いたのはいいけどよくわかりません。そういう時は問題を小さく切り分けることが大事です。
directory構成(会社の先輩の記事から拝借、、、有難うございます)

$ tree -I node_modules
.
├── node_modules
├── .eslintrc.js
├── .prettierrc
├── README.md
├── nest-cli.json
├── package-lock.json
├── package.json
├── src
│   ├── app.controller.spec.ts
│   ├── app.controller.ts
│   ├── app.module.ts
│   ├── app.service.ts
│   └── main.ts
├── test
│   ├── app.e2e-spec.ts
│   └── jest-e2e.json
├── tsconfig.build.json
└── tsconfig.json
  • node_modules
    • プロジェクトに依存しているライブラリ群
  • src
    • プロダクションコードを格納するディレクトリ
    • specがついたファイルはテストファイル
    • main.ts はプロジェクトのエントリーポイントになる
    • mainから始まり各種ファイルが読み込まれたのちコード内記載のポート番号でリスンする
  • test
    • テストコードを格納するディレクトリ
  • .eslintrc.js
    • ES Lintの設定ファイル
  • .prettierrc
    • コード整形を行うprettirの設定ファイル
  • nest-cli.json
    • Nestプロジェクトの設定を上書きしたり、追加するためのファイル
  • package-lock.json
    • プロジェクト情報
    • npmスクリプト
    • ライブラリの依存情報
    • などなどが記載
  • tsconfig.build.json / tsconfig.json
    • タイプスクリプトの設定ファイル

srcディレクトリのコアファイルの概要は以下の通り。

app.controller.ts 簡便なシングルルート用コントローラ
app.controller.spec.ts ユニットテスト用コントローラ
app.module.ts アプリケーションのルートモジュール
app.service.ts シングルメソッドの簡便なサービス
main.ts NestFactory機能を使いNest アプリケーションインスタンスを作る為のファイル

main.ts

Nest.js実行時に最初main.tsでbootstrapを実行してappというNestアプリケーションインスタンスを作成する。インタンスを作成する過程でAppModule渡す

main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();

app.module.ts

AppModuleというのは、アプリケーションのルートモジュールになります。各アプリケーションは、少なくとも1つのモジュール(ルートモジュール)を持っている。ルートモジュールはNestはアプリケーションのツリー構造(つまり、モジュールやプロバイダの関係・依存関係を解決する為に使われる内部データ構造)を構築する為の出発点です。

@Module()デコレータは、モジュールを管理するプロパティを持つ単一のオブジェクトを受け付けます。構成要素としては下記になります。

providers Nestのインジェクタによってインスタンス化され、少なくともこのモジュールで共有される可能性のあるプロパイダ一覧→例)Service(@Injectavle()デコレータがついたクラス)
controllers インスタンス化された、このモジュールで宣言されるコントローラ一覧(@Controllerデコレータがついたクラス)
imports このモジュールで必要なプロパイダをエクスポートしているモジュールの一覧
exports このモジュールをインポートしている別のモジュールで使用されるプロバイダ一覧(このモジュールが提供しているプロバイダ一覧)
app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

ルートモジュールでは、@ModuleデコレータでAppController,AppServiceを登録し、AppModuleをインスタンス化するときにAppControllerとAppServiceを次々にインスタンス化している↓イメージ図

app.controller.ts(テストファイルには触れないでおきます)

Controllerでは@Controller()デコレータでクラスを定義している。コントローラの目的はアプリケーションの特定のリクエストを受け取ること。どのコントローラがどのリクエストを受けるか、ルーティング機構が制御する。
ここでAPIのエンドポイントやレスポンスの内容を記述する

app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}

app.service.ts

プロバイダーは、Nest の基本的な概念です。基本的な Nest クラスの多くは、プロバイダー (サービス、リポジトリ、ファクトリ、ヘルパーなど) として扱われる場合があります。プロバイダーの主な考え方は、依存関係として注入できるということです。これは、オブジェクトが相互にさまざまな関係を作成できることを意味し、オブジェクトのインスタンスを「結び付ける」機能の大部分を Nest ランタイム システムに委任できます。
https://docs.nestjs.com/providers

Nestはアーキテクチャの設計でSOLIDの原則に従っているみたいです。クラスはあくまでも単一の責務を負うべきという考え方です。一つ以上の責務があると変更とかがあった時にあそこもあそこも直さないとってなるから大変だよねってことらしいです。
https://en.wikipedia.org/wiki/SOLID

Controllerの役割はGetでAPIのエンドポイントがリクエストされたら、getHello()を実行する。getHello()の中身はapp.serviceのgetHello()の実行を返却するよということです。(書いていてわけわかりませんね。読み飛ばしていいです)
コントローラーにはサービスのgetHello()の中身が記述されていないです。
関数の中身(ビジネスロジック)はServiceで記述するからコントローラは考えなくていいよっていうことが責任を分けることだと解釈しました。
ちっちゃくアプリを作ろうと思えば気にしなくてもいいことなんですが、
昨今の大規模開発では、ちゃんと責務を分けてある方が機能の変更や追加の時に開発者にとっては嬉しいはずです。後々のことを考えて先人たちが設計してくれたんですね。ありがたやありがたや、、、

app.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }
}

結論

NestJSについてチョットわかりましたね。なんもわからん状態から雰囲気わかるにレベルアップしたなら上出来だと思います(自分に言い聞かせる)
開発では、Module(それぞれのモジュールに合わせてコントローラーとかサービスを作る)をたくさん作ってExportして使います。(もちろんルートモジュールはImportします)

開発の足がかりができたので嬉しいです。

余談

ドキュメントにProviderって書いてあるやん、サービスちゃうやん最初から統一してくれと思いました。

DI(Dependency Injection)

サービスでDIって出てきたけど、正直よくわからん。
先輩の記事ではコントローラに渡すサービスのインスタンスは外部から渡した方がいいよって書いてありました。
使ってみないとありがたみがわからないと仮定して次出会うまで待ちます。その時に記事にします。

キーワードは依存関係を弱めることにある!ということだけ覚えておきます。

Nest は、一般にDependency Injectionとして知られる強力な設計パターンを中心に構築されています。Angularの公式ドキュメントで、この概念に関する優れた記事を読むことをお勧めします。
Nest では、TypeScript 機能のおかげで、依存関係は型だけで解決されるため、依存関係の管理が非常に簡単です。
https://docs.nestjs.com/providers

DIについてAngularの優れた記事↓
https://angular.io/guide/dependency-injection

Discussion