nest generateコマンドからざっくりとNestJSを把握する

:::note
この記事は「[mofmof Advent Calendar 2023](https://qiita.com/advent-calendar/2023/mofmof)」11日目の記事です。
:::

:::note
株式会社mofmofは受託開発会社ですが、メンバー自らがオーナーとなってプロダクト開発にチャレンジしています。
この記事は、NestJS, 生成AIにチャレンジする「金曜日のチーム開発」の成果です。

https://indie-dev.mof-mof.co.jp/
:::

今回学習したNestJSと生成AIを使って作ったサービスはこちら
よかったら使ってみてください！

https://www.argos.tokyo/

# はじめに

https://docs.nestjs.com/cli/usages#nest-generate

NestJSのCLIコマンド`nest generate`で生成されるファイル・ディレクトリから、NestJSプロジェクトの構成やNestJSの敷いてくれるレールをざっくりと把握します。

# プロジェクト作成
```sh
$ npm i -g @nestjs/cli
$ nest new hello_nest
⚡  We will scaffold your app in a few seconds..

? Which package manager would you ❤️  to use? npm
CREATE hello_nest/.eslintrc.js (663 bytes)
CREATE hello_nest/.prettierrc (51 bytes)
CREATE hello_nest/README.md (3340 bytes)
CREATE hello_nest/nest-cli.json (171 bytes)
CREATE hello_nest/package.json (1951 bytes)
CREATE hello_nest/tsconfig.build.json (97 bytes)
CREATE hello_nest/tsconfig.json (546 bytes)
CREATE hello_nest/src/app.controller.ts (274 bytes)
CREATE hello_nest/src/app.module.ts (249 bytes)
CREATE hello_nest/src/app.service.ts (142 bytes)
CREATE hello_nest/src/main.ts (208 bytes)
CREATE hello_nest/src/app.controller.spec.ts (617 bytes)
CREATE hello_nest/test/jest-e2e.json (183 bytes)
CREATE hello_nest/test/app.e2e-spec.ts (630 bytes)

✔ Installation in progress... ☕

🚀  Successfully created project hello_nest
👉  Get started with the following commands:

$ cd hello_nest
$ npm run start

                                         
                          Thanks for installing Nest 🙏
                 Please consider donating to our open collective
                        to help us maintain this package.
                                         
                                         
               🍷  Donate: https://opencollective.com/nest
```

```sh
$ tree -I node_modules
.
├── 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
```

# nest generateコマンド

```sh
$ npx nest g <schematic> <name> [options]
```

## Schematics

| 名前           | エイリアス | 説明                                                                                     |
|--------------|---------|----------------------------------------------------------------------------------------|
| `app`        |         | モノレポ内に新しいアプリケーションを生成 (標準構造の場合はモノレポに変換)。                |
| `library`    | `lib`   | モノレポ内に新しいライブラリを生成 (標準構造の場合はモノレポに変換)。                    |
| `class`      | `cl`    | 新しいクラスを生成。                                                                     |
| `controller` | `co`    | コントローラー宣言を生成。                                                               |
| `decorator`  | `d`     | カスタムデコレーターを生成。                                                             |
| `filter`     | `f`     | フィルター宣言を生成。                                                                   |
| `gateway`    | `ga`    | ゲートウェイ宣言を生成。                                                                 |
| `guard`      | `gu`    | ガード宣言を生成。                                                                       |
| `interface`  | `itf`   | インターフェースを生成。                                                                 |
| `interceptor`| `itc`   | インターセプター宣言を生成。                                                             |
| `middleware` | `mi`    | ミドルウェア宣言を生成。                                                                 |
| `module`     | `mo`    | モジュール宣言を生成。                                                                   |
| `pipe`       | `pi`    | パイプ宣言を生成。                                                                       |
| `provider`   | `pr`    | プロバイダー宣言を生成。                                                                 |
| `resolver`   | `r`     | リゾルバー宣言を生成。                                                                   |
| `resource`   | `res`   | 新しいCRUDリソースを生成。                                                               |
| `service`    | `s`     | サービス宣言を生成。                                                                     |

### app

`nest new`した時に自動生成されるものと同じ。
開発の基盤となるアプリケーションの基本的な構造、主要なモジュール、コントローラー、サービスが含まれる。

<details><summary>generateコマンド実行結果</summary>

```
$ nest g app
? What name would you like to use for the app? 
DELETE src
DELETE test
CREATE apps/hello_nest/tsconfig.app.json (225 bytes)
CREATE apps/hello_nest/src/app.controller.spec.ts (617 bytes)
CREATE apps/hello_nest/src/app.controller.ts (274 bytes)
CREATE apps/hello_nest/src/app.module.ts (249 bytes)
CREATE apps/hello_nest/src/app.service.ts (142 bytes)
CREATE apps/hello_nest/src/main.ts (208 bytes)
CREATE apps/hello_nest/test/app.e2e-spec.ts (630 bytes)
CREATE apps/hello_nest/test/jest-e2e.json (183 bytes)
CREATE apps/app/tsconfig.app.json (218 bytes)
CREATE apps/app/src/main.ts (208 bytes)
CREATE apps/app/src/app.controller.spec.ts (617 bytes)
CREATE apps/app/src/app.controller.ts (274 bytes)
CREATE apps/app/src/app.module.ts (249 bytes)
CREATE apps/app/src/app.service.ts (142 bytes)
CREATE apps/app/test/jest-e2e.json (183 bytes)
CREATE apps/app/test/app.e2e-spec.ts (630 bytes)
UPDATE tsconfig.json (562 bytes)
UPDATE package.json (2026 bytes)
UPDATE nest-cli.json (813 bytes)
```
モノレポに変換された！
```sh
❯ tree -I node_modules
.
├── README.md
├── apps
│   ├── app
│   │   ├── 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.app.json
│   └── hello_nest
│       ├── 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.app.json
├── nest-cli.json
├── package-lock.json
├── package.json
├── tsconfig.build.json
└── tsconfig.json
```
</details>

### library
モノレポ内でライブラリの雛形(モジュールやサービス等)を生成する。

<details><summary>generateコマンド実行結果</summary>

```sh
$ nest g library
? What name would you like to use for the library? hoge
? What prefix would you like to use for the library (default: @app)? 
CREATE libs/hoge/tsconfig.lib.json (218 bytes)
CREATE libs/hoge/src/index.ts (63 bytes)
CREATE libs/hoge/src/hoge.module.ts (182 bytes)
CREATE libs/hoge/src/hoge.service.spec.ts (446 bytes)
CREATE libs/hoge/src/hoge.service.ts (88 bytes)
UPDATE nest-cli.json (1040 bytes)
UPDATE package.json (2140 bytes)
UPDATE tsconfig.json (678 bytes)
```
ルートに`libs`ディレクトリが生成される。package.jsonのprojectsにも追加されていた。
```shell
$ cd libs/
$ tree .
.
└── hoge
    ├── src
    │   ├── hoge.module.ts
    │   ├── hoge.service.spec.ts
    │   ├── hoge.service.ts
    │   └── index.ts
    └── tsconfig.lib.json
```
</details>

### class
新しいクラスを生成。

<details><summary>generateコマンド実行結果</summary>

```sh
$ nest g class hoge
? Which project would you like to generate to? hello_nest [ Default ]
CREATE apps/hello_nest/src/hoge/hoge.spec.ts (139 bytes)
CREATE apps/hello_nest/src/hoge/hoge.ts (21 bytes)
```
</details>

### controller
アプリケーションのルーティングを管理し、リクエストを受け取りレスポンスを返すコントローラーの雛形を生成。

<details><summary>実行結果</summary>

```
$ nest g controller hoge
? Which project would you like to generate to? hello_nest [ Default ]
CREATE apps/hello_nest/src/hoge/hoge.controller.spec.ts (478 bytes)
CREATE apps/hello_nest/src/hoge/hoge.controller.ts (97 bytes)
UPDATE apps/hello_nest/src/app.module.ts (322 bytes)
```
</details>

### decorator
クラス、メソッド等への追加機能やメタデータを提供するためのカスタムデコレーターの雛形を生成。

NestJSでは沢山のデコレーターが利用されており、自分で定義したデコレーターも使える。
複数デコレーターを適用する場合は下から順番に関数が実行されることに注意！
```ts:hoge.decorator.ts
import { Logger, SetMetadata } from '@nestjs/common';

export const Hoge = (...args: string[]) => {
  Logger.log('Hoge decorator called');
  return SetMetadata('hoge', args); // hogeというkeyに対してデコレーターの引数で受け取った値をセット
};
```

```ts:example.service.ts
import { Injectable, Logger, Reflector } from '@nestjs/common';
import { Hoge } from './hoge.decorator';

@Injectable()
export class ExampleService {
  constructor(private reflector: Reflector) {}

  @Hoge('fuga')
  someMethod() {
    const metadata = this.reflector.get<string[]>('hoge', this.someMethod);
    Logger.log(metadata); // fugaがログに出力される
  }
}
```

<details><summary>generateコマンド実行結果</summary>

```
$ nest g decorator hoge
? Which project would you like to generate to? hello_nest [ Default ]
CREATE apps/hello_nest/src/hoge/hoge.decorator.ts (117 bytes)
```

```ts:hoge.decorator.ts
import { SetMetadata } from '@nestjs/common';

export const Hoge = (...args: string[]) => SetMetadata('hoge', args);
```
</details>

### filter
アプリケーションで発生する例外をキャッチし、カスタムエラーレスポンスを提供するフィルターを生成。

filterは特定の例外に対してグローバルに適用したり、特定のコントローラー等に適用したりすることができる。

```ts:hoge.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';

@Catch(HttpException)
export class HogeFilter<HttpException> implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const status = exception.getStatus();

    response
      .status(status)
      .json({
        statusCode: status,
        timestamp: new Date().toISOString(),
        path: ctx.getRequest().url,
      });
  }
}
```
グローバルに適用する場合はmain.tsでuseGlobalFiltersにインスタンスを渡す。
```ts:main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { HogeFilter } from './common/filters/hoge.filter';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new HogeFilter());
  await app.listen(3000);
}
bootstrap();
```
特定のコントローラー等に適用する場合はデコレーターを使う。
```ts:example.controller.ts
import { Controller, Get, UseFilters } from '@nestjs/common';
import { HogeFilter } from './common/filters/hoge.filter';

@Controller('example')
@UseFilters(new HogeFilter())
export class ExampleController {
  @Get()
  someMethod() {
    // ...
  }
}
```

<details><summary>generateコマンド実行結果</summary>

```sh
$ nest g filter hoge
? Which project would you like to generate to? hello_nest [ Default ]
CREATE apps/hello_nest/src/hoge/hoge.filter.spec.ts (164 bytes)
CREATE apps/hello_nest/src/hoge/hoge.filter.ts (186 bytes)
```

```ts:hello.filter.ts
import { ArgumentsHost, Catch, ExceptionFilter } from '@nestjs/common';

@Catch()
export class HogeFilter<T> implements ExceptionFilter {
  catch(exception: T, host: ArgumentsHost) {}
}
```
</details>

### gateway
WebSocketベースの通信を処理するためのゲートウェイを生成。
チャットなどのリアルタイム通信を実現するために使う。

<details><summary>generateコマンド実行結果</summary>

```
$ nest g gateway hoge
? Which project would you like to generate to? hello_nest [ Default ]
CREATE apps/hello_nest/src/hoge/hoge.gateway.spec.ts (446 bytes)
CREATE apps/hello_nest/src/hoge/hoge.gateway.ts (238 bytes)
UPDATE apps/hello_nest/src/app.module.ts (386 bytes)
```

```ts:hoge.gateway.ts
import { SubscribeMessage, WebSocketGateway } from '@nestjs/websockets';

@WebSocketGateway()
export class HogeGateway {
  @SubscribeMessage('message')
  handleMessage(client: any, payload: any): string {
    return 'Hello world!';
  }
}
```
</details>

### guard
特定のルートへのアクセスを制御するためのガードを生成。
認証や認可などのセキュリティ機能を実装。

```ts:auth.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest();
    return this.validateRequest(request);
  }

  private validateRequest(request): boolean {
    // ここでリクエストを検証してbooleanで返す
    return request.session && request.session.user;
  }
}
```

```ts:example.controller.ts
import { Controller, UseGuards } from '@nestjs/common';
import { AuthGuard } from './guards/auth.guard';

@Controller('example')
@UseGuards(AuthGuard)
export class ExampleController {
  // ...
}
```

<details><summary>generateコマンド実行結果</summary>

```
$ nest g guard hoge
? Which project would you like to generate to? hello_nest [ Default ]
CREATE apps/hello_nest/src/hoge/hoge.guard.spec.ts (160 bytes)
CREATE apps/hello_nest/src/hoge/hoge.guard.ts (299 bytes)
```

```ts:hoge.guard.ts
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class HogeGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    return true;
  }
}
```
</details>

### interface
TypeScriptのインターフェースを生成。

<details><summary>generateコマンド実行結果</summary>

```
$ nest g interface hoge
? Which project would you like to generate to? hello_nest [ Default ]
CREATE apps/hello_nest/src/hoge/hoge.interface.ts (25 bytes)
```
</details>

### interceptor
リクエストやレスポンスを処理する前後に追加の処理(ロギングやエラー処理など)を実行するインターセプターを生成。


```ts:logging.interceptor.ts
import { Injectable, Logger, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const now = Date.now();
    const method = context.getHandler().name; // メソッド名を取得
    const className = context.getClass().name; // クラス名を取得

    // 実行前のログ
    Logger.log(`Before... Method: ${method}, Class: ${className}`);
    
    return next
      .handle()
      .pipe(
        // 実行後のログ(実行時間を記録)
        tap(() => Logger.log(`After... Method: ${method}, Class: ${className}, ${Date.now() - now}ms`)), 
      );
  }
}
```

グローバルに適用する場合はmain.tsでuseGlobalInterceptorsにインスタンスを渡す。
```ts:main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { LoggingInterceptor } from './interceptors/logging.interceptor';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalInterceptors(new LoggingInterceptor());
  await app.listen(3000);
}
bootstrap();
```
特定のコントローラー等に適用する場合はデコレーターを使う。
```ts:example.controller.ts
import { Controller, Get, UseFilters } from '@nestjs/common';
import { LoggingInterceptor } from './interceptors/logging.interceptor';

@Controller('example')
@UseInterceptors(LoggingInterceptor)
export class ExampleController {
  @Get()
  someMethod() {
    // ...
  }
}
```

<details><summary>generateコマンド実行結果</summary>

```
$ nest g interceptor hoge
? Which project would you like to generate to? hello_nest [ Default ]
CREATE apps/hello_nest/src/hoge/hoge.interceptor.spec.ts (184 bytes)
CREATE apps/hello_nest/src/hoge/hoge.interceptor.ts (310 bytes)
```

```ts:hoge.interceptor.ts
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class HogeInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle();
  }
}
```
</details>

### middleware
リクエスト処理パイプラインにカスタムロジックを追加するためのミドルウェアを生成。

<details><summary>generateコマンド実行結果</summary>

```
$ nest g middleware hoge
? Which project would you like to generate to? hello_nest [ Default ]
CREATE apps/hello_nest/src/hoge/hoge.middleware.spec.ts (180 bytes)
CREATE apps/hello_nest/src/hoge/hoge.middleware.ts (196 bytes)
```

```ts:hoge.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';

@Injectable()
export class HogeMiddleware implements NestMiddleware {
  use(req: any, res: any, next: () => void) {
    next();
  }
}
```
</details>

### module
関連するコントローラーやサービスをグループ化するためのモジュールを生成。

<details><summary>generateコマンド実行結果</summary>

```
$ nest g module hoge
? Which project would you like to generate to? hello_nest [ Default ]
CREATE apps/hello_nest/src/hoge/hoge.module.ts (81 bytes)
UPDATE apps/hello_nest/src/app.module.ts (445 bytes) 
```

```ts:hoge.module.ts
import { Module } from '@nestjs/common';

@Module({})
export class HogeModule {}
```
</details>

### pipe
リクエストのデータ変換やバリデーションを行うパイプを生成。

大文字に変換するpipeのサンプル↓
```ts:uppercase.pipe.ts
import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common';

@Injectable()
export class UppercasePipe implements PipeTransform {
  transform(value: string, metadata: ArgumentMetadata): string {
    return value.toUpperCase();
  }
}
```

```ts:example.controller.ts
import { Controller, Get, Param } from '@nestjs/common';
import { UppercasePipe } from './pipes/uppercase.pipe';

@Controller('example')
export class ExampleController {
  @Get(':name')
  getGreeting(@Param('name', UppercasePipe) name: string) {
    return `Hello, ${name}!`;
  }
}
```

<details><summary>generateコマンド実行結果</summary>

```
$ nest g pipe hoge
? Which project would you like to generate to? hello_nest [ Default ]
CREATE apps/hello_nest/src/hoge/hoge.pipe.spec.ts (156 bytes)
CREATE apps/hello_nest/src/hoge/hoge.pipe.ts (220 bytes)
```

```ts:hoge.pipe.ts
import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common';

@Injectable()
export class HogePipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    return value;
  }
}
```
</details>

### provider
依存注入のためのサービス、ファクトリー、ヘルパーなどのプロバイダーを生成。
@Injectableデコレータのついたクラスを生成して、moduleに登録するところまで。
あとは使いたいControllerのconstructorでDIして使えるようになる。

<details><summary>generateコマンド実行結果</summary>

```
$ nest g provider hoge
? Which project would you like to generate to? hello_nest [ Default ]
CREATE apps/hello_nest/src/hoge/hoge.spec.ts (399 bytes)
CREATE apps/hello_nest/src/hoge/hoge.ts (81 bytes)
UPDATE apps/hello_nest/src/hoge/hoge.module.ts (133 bytes) 
```

```ts:hoge.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class Hoge {}
```
</details>

### resolver
GraphQL APIを構築する際に使用する。
GraphQLクエリやミューテーションを処理するリゾルバー雛形を生成。

```ts:hoge.resolver.ts
import { Query, Resolver } from '@nestjs/graphql';

@Resolver()
export class HogeResolver {
  @Query(() => String)
  hello(): string {
    return 'Hello World!';
  }
}
```

<details><summary>generateコマンド実行結果</summary>

```
$ nest g resolver hoge
? Which project would you like to generate to? hello_nest [ Default ]
CREATE apps/hello_nest/src/hoge/hoge.resolver.spec.ts (456 bytes)
CREATE apps/hello_nest/src/hoge/hoge.resolver.ts (86 bytes)
UPDATE apps/hello_nest/src/hoge/hoge.module.ts (226 bytes) 
```

```ts:hoge.resolver.ts
import { Resolver } from '@nestjs/graphql';

@Resolver()
export class HogeResolver {}
```
</details>

### resource
新しいCRUDリソースを生成する。基本的なCRUD操作を実装したモジュール、コントローラー、サービスを含む。
トランスポート層に何を使うか選択できる
(REST API, GraphQL(code first), GraphQL(schema first), Microservice(non-HTTP), WebSockets)

<details><summary>generateコマンド実行結果</summary>

```
$ nest g resource
? Which project would you like to generate to? hello_nest [ Default ]
? What name would you like to use for this resource (plural, e.g., "users")? users
? What transport layer do you use? REST API
? Would you like to generate CRUD entry points? Yes
CREATE apps/hello_nest/src/users/users.controller.spec.ts (566 bytes)
CREATE apps/hello_nest/src/users/users.controller.ts (894 bytes)
CREATE apps/hello_nest/src/users/users.module.ts (248 bytes)
CREATE apps/hello_nest/src/users/users.service.spec.ts (453 bytes)
CREATE apps/hello_nest/src/users/users.service.ts (609 bytes)
CREATE apps/hello_nest/src/users/dto/create-user.dto.ts (30 bytes)
CREATE apps/hello_nest/src/users/dto/update-user.dto.ts (169 bytes)
CREATE apps/hello_nest/src/users/entities/user.entity.ts (21 bytes)
UPDATE package.json (2173 bytes)
UPDATE apps/hello_nest/src/app.module.ts (510 bytes)
✔ Packages installed successfully.
```
</details>

### service
ビジネスロジックを処理するサービスを生成。

<details><summary>generateコマンド実行結果</summary>

```
$ nest g service hoge
? Which project would you like to generate to? hello_nest [ Default ]
CREATE apps/hello_nest/src/hoge/hoge.service.spec.ts (446 bytes)
CREATE apps/hello_nest/src/hoge/hoge.service.ts (88 bytes)
UPDATE apps/hello_nest/src/hoge/hoge.module.ts (285 bytes)
```

```ts:hoge.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class HogeService {}
```
</details>

## Options

| オプション       | エイリアス | 説明                                                                                       |
|----------------|---------|------------------------------------------------------------------------------------------|
| `--dry-run`    | `-d`    | ファイルシステムへの変更を行わずに変更内容を表示。                                  |
| `--project`    | `-p`    | 要素を追加するプロジェクトを指定。                                                   |
| `--flat`       |         | 要素用のフォルダを生成しないようにする。                                                            |
| `--collection` | `-c`    | インストールされたnpmパッケージのパッケージ名を指定することでそのパッケージ内からSchematicsを検索して利用する。 |
| `--spec`       |         | スペックファイルの生成（デフォルト）。                                         |
| `--no-spec`    |         | スペックファイルを生成しないようにする。                                                      |

# 最後に

`nest generate`コマンドについて、一通りみてみました。
これを機にあまり馴染みのなかった`interceptor`や`guard`などの概要を知ることができたので、実装の幅が広がったような気がします。

ここまで読んでいただき、ありがとうございました！