🙄

NestJsのdocsをやってみる

2024/11/21に公開

はじめに

nestjsのdocsを触りながらメモを残していく
殴り書きのメモなので悪しからず。

OVERVIEW

環境構築

nodeはインストール済みなのでプロジェクトを作るところから

npm i -g @nestjs/cli
nest new project-name

ファイル構造・概要

そもそも

npmで起動した時にmain.tsが明示していないのに呼ばれるのはnest-cli.jsonで"sourceRoot": "src",が指定されているからっぽい。
main.tsのエントリーポイントになる関数がbootstrapという名前は慣習的なもので、自力で起動するという意味があるらしい。

設計モデル

MVCモデルという、プログラムを役割ごとにModel(モデル)・View(ビュー)・Controller(コントローラー)の3つに分けて管理するソフトウェア設計モデルを使っている。

  • Model
    • ビジネスロジックを担当。DBとの接続とか。
  • View
    • 表示や入出力などのユーザーインターフェースを担当。
  • Controller
    • ModelとViewの制御を担当。ユーザーとの繋ぎになる部分で、ModelやViewに指示を送る。テレビのリモコンと同じイメージ。
src
├── app.controller.spec.ts
├── app.controller.ts
├── app.module.ts
├── app.service.ts
└── main.ts

main.ts

エントリーポイント

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
  • NestFactoryはアプリのインスタンスを作るためのクラス。モジュールをapp化する。
  • 作成したappをポート3000で公開。
  • async/awaitについては非同期処理を調べること。

app.controller.ts

ルーティングを管理する

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

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}
  • @Controllerというデコレーターの引数にパスを指定することで、クラス内のルーティングのprefixを設定できる。
  • constructorはクラスが作成される時に動くもので、引数にAppServiceをappServiceとして受け取り、インスタンス化している。
  • AppServiceはprivate属性なので、AppControllerクラスの中からしか呼び出すことはできない。(安全)
  • @GetはGETリクエストのときに動く関数。今回だとappService.getHelloを呼び出すようにしている。具体的なビジネスロジックはserviceに書かれる。

app.service.ts

ビジネスロジックが書かれる

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }
}
  • @InjectableデコレーターはNestjsにおいて、クラス間の依存関係を解決するため(簡単に書くため)に使われる。これをDI(Dependency Injection)という。

app.module.ts

モジュール化を行い、依存関係を解決する

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
  • ここではAppControllerとAppServiceをまとめてAppModuleとしてモジュール化している。
  • importsに別のmoduleをかくことで使用することができる
  • providers
    • providerの意味の通り、色々機能を提供するものが入る。今回だと実際の処理を提供するServiceを指定している。

*.spec.ts

  • テストコードが書かれている

Controllers

以下のコマンドでcuiでコントローラーを作ることができる

nest g controller [name]

仮にcatsというコントローラーを作成すると、

src
├── app.controller.spec.ts
├── app.controller.ts
├── app.module.ts
├── app.service.ts
├── cats
│   ├── cats.controller.spec.ts
│   └── cats.controller.ts
└── main.ts
// cats.controller.ts
...
@Controller('cats')
export class CatsController {
  @Get()
  findAll(): string {
    return 'This action returns all cats';
  }
}

上記のように、作成されたcontrollerクラスに適当なゲッターを追加すると、localhost:3000/catsに送られてきたGETリクエストを処理することができるようになる。

Request object

import { Controller, Get, Req } from '@nestjs/common';
import { Request } from 'express';

@Controller('cats')
export class CatsController {
  @Get()
  // ここ
  findAll(@Req() request: Request): string {
    return 'This action returns all cats';
  }
}

上記のコードのように@Req()と書くだけでレスポンスクラスにアクセスすることができる。わざわざ手動でボディやヘッダーを書かずに、デコレーターをつけることでアクセスすることができる。ここには他にも@Res()や、@Bosy()など色々かける。

StatusCode

レスポンスにはステータスコードが含まれる。nestでは(POSTを除いて)デフォルトだと200を返すようになっている。先ほどのGETのコントローラーも200を返す。
もしステータスコードを変更したい場合は下記のようにすることで変更できる
例1)

import {Controller, Get, HttpCode} from '@nestjs/common';
...
@Get()
// ここの200を変更する
@HttpCode(200)
findAll(): string {
  return 'This action returns all cats';
}

例2)

import {Controller, Get, Res} from '@nestjs/common';
import {Response} from 'express';
...
@Get()
// responseを受け取り、statusを変更してsendする
findAll(@Res() response: Response): void {
  response.status(220).send('This action returns all cats');
}

Providers

NestではDI(Dependency Injection)というデザインパターンを中心に構築されている。
これはTypescriptベースのオープンソースフレームワークであるAngularの公式DocsのDIについて読むべきらしい。ざっと読んだ。

DI

意味

「依存する他のオブジェクトや関数を注入する」意味。
依存性の注入という訳らしいが、依存オブジェクト注入という言い方のほうがわかりやすいという人も多いのだとか。たしかに後者の方がイメージしやすい。

目的

コンポーネント(nestにおいてはクラス?)間の関係性を薄くすること。
メリットとして、

  • 他のクラスに依存しない(薄い)のでコードの修正時に変更がしやすい
  • 上に同じく単体テストの作成と変更がしやすい
    などが挙げられるようだ。

構造

コンポーネント間の関係はインターフェースを用いて記述して具体的なコンポーネントを指定しない。

Modules

Shared modules

// cats.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

@Module({
  controllers: [CatsController],
  providers: [CatsService],
  exports: [CatsService]
})
export class CatsModule {}

nestではモジュールはデフォルトでシングルトン(単一のインスタンス)として管理される。
プロバイダー(サービス)も"モジュール内"ではデフォルトでシングルトンとして管理される。これを他のモジュールでもインスタンスを共有したい場合はexportsする必要がある。

Module re-exporting

モジュールを再エクスポートをすることで、インポートしたモジュールも再エクスポートできる。

@Module({
  imports: [CommonModule],
  exports: [CommonModule],
})
export class CoreModule {}

再エクスポートを使用しない場合:

@Module({
  imports: [CommonModule],
})
export class FeatureModule {}

@Module({
  imports: [CommonModule],
})
export class AnotherFeatureModule {}

再エクスポートを使用する場合:

@Module({
  imports: [CommonModule],
  exports: [CommonModule],
})
export class CoreModule {}

@Module({
  imports: [CoreModule],
})
export class FeatureModule {}

@Module({
  imports: [CoreModule],
})
export class AnotherFeatureModule {}

こうすることで、CommonModuleも間接的にインポートされる。

Dependency injection

モジュールクラス内でプロバイダーを注入することもできる。

import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {
  constructor(private catsService: CatsService) {}
}

Global modules

モジュールに@Global()デコレーターをつけ、exportすることで、どこからでもアクセスできるようになる。

import { Module, Global } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

@Global()
@Module({
  controllers: [CatsController],
  providers: [CatsService],
  exports: [CatsService],
})
export class CatsModule {}

グローバルモジュールはアプリケーション全体に一度だけ登録される。他から注入する場合はimportsは書く必要がなくなる。

Global modules

一旦動的にモジュールを設定するための機能という理解でとめておきます。

Discussion