📝

NestJSでmonorepoがいい感じ

2023/04/12に公開

何らかのアプリケーションと、その管理アプリケーションを開発するみたいなよくあるケース、
NestJSでサポートされているmonorepo機能を使うといい感じだったので備忘録も兼ねて

やってみる

1. NestJSプロジェクトの作成

まずは通常通りnest newでNestJSプロジェクトを新規作成します

bash
npx nest new my-app

するとディレクトリ構成はこんな感じになります

.
├── 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

2. 管理アプリケーションを追加する

では先ほど作ったmy-appの管理アプリケーションとしてadmin-app作っていきます

bash
npx nest g app admin-app

するとディレクトリ構成が変更されて以下のようにapps/配下にmy-appadmin-appが配置されます

.
├── README.md
├── apps
│   ├── admin-app
│   │   ├── src
│   │   │   ├── admin-app.controller.spec.ts
│   │   │   ├── admin-app.controller.ts
│   │   │   ├── admin-app.module.ts
│   │   │   ├── admin-app.service.ts
│   │   │   └── main.ts
│   │   ├── test
│   │   │   ├── app.e2e-spec.ts
│   │   │   └── jest-e2e.json
│   │   └── tsconfig.app.json
│   └── my-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
├── nest-cli.json
├── package-lock.json
├── package.json
├── tsconfig.build.json
└── tsconfig.json

3. それぞれ起動してみる

nest-cliでそれぞれのアプリを起動します
nest-cliの設定はnest-cli.jsonにあり、先ほど実行したコマンドに応じて自動で更新してくれています
projectsにあるのがそれぞれのアプリの設定ですね

nest-cli.json
{
  "$schema": "https://json.schemastore.org/nest-cli",
  "collection": "@nestjs/schematics",
  "sourceRoot": "apps/my-app/src",
  "monorepo": true,
  "root": "apps/my-app",
  "compilerOptions": {
    "webpack": true,
    "tsConfigPath": "apps/my-app/tsconfig.app.json"
  },
  "projects": {
    "my-app": {
      "type": "application",
      "root": "apps/my-app",
      "entryFile": "main",
      "sourceRoot": "apps/my-app/src",
      "compilerOptions": {
        "tsConfigPath": "apps/my-app/tsconfig.app.json"
      }
    },
    "admin-app": {
      "type": "application",
      "root": "apps/admin-app",
      "entryFile": "main",
      "sourceRoot": "apps/admin-app/src",
      "compilerOptions": {
        "tsConfigPath": "apps/admin-app/tsconfig.app.json"
      }
    }
  }
}

起動にあたり、my-appadmin-appが同じPORTで起動する設定になっている箇所だけ変更しておきます

apps/admin-app/src/main.ts
import { NestFactory } from '@nestjs/core';
import { AdminAppModule } from './admin-app.module';

async function bootstrap() {
  const app = await NestFactory.create(AdminAppModule);
  await app.listen(3001); // 3000から3001に
}
bootstrap();

以下コマンドで起動

bash
npx nest start my-app
npx nest start admin-app

4. 共通レイヤーも作成してみる

NestJSでは各アプリから利用する共通コードをlibraryとして管理することができます
my-coreというlibraryを作ってみます

npx nest g library my-core

作成するとディレクトリ構成は以下のようになります

.
├── README.md
├── apps
│   ├── admin-app
│   │   ├── src
│   │   │   ├── admin-app.controller.spec.ts
│   │   │   ├── admin-app.controller.ts
│   │   │   ├── admin-app.module.ts
│   │   │   ├── admin-app.service.ts
│   │   │   └── main.ts
│   │   ├── test
│   │   │   ├── app.e2e-spec.ts
│   │   │   └── jest-e2e.json
│   │   └── tsconfig.app.json
│   └── my-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
├── dist
│   └── apps
│       ├── admin-app
│       │   └── main.js
│       └── my-app
│           └── main.js
├── libs
│   └── my-core
│       ├── src
│       │   ├── index.ts
│       │   ├── my-core.module.ts
│       │   ├── my-core.service.spec.ts
│       │   └── my-core.service.ts
│       └── tsconfig.lib.json
├── nest-cli.json
├── package-lock.json
├── package.json
├── tsconfig.build.json
└── tsconfig.json

nest-cli.jsonのprojectsにも、"type":"library"として追加されています

nest-cli.json
{
  "$schema": "https://json.schemastore.org/nest-cli",
  "collection": "@nestjs/schematics",
  "sourceRoot": "apps/my-app/src",
  "monorepo": true,
  "root": "apps/my-app",
  "compilerOptions": {
    "webpack": true,
    "tsConfigPath": "apps/my-app/tsconfig.app.json"
  },
  "projects": {
    "my-app": {
      "type": "application",
      "root": "apps/my-app",
      "entryFile": "main",
      "sourceRoot": "apps/my-app/src",
      "compilerOptions": {
        "tsConfigPath": "apps/my-app/tsconfig.app.json"
      }
    },
    "admin-app": {
      "type": "application",
      "root": "apps/admin-app",
      "entryFile": "main",
      "sourceRoot": "apps/admin-app/src",
      "compilerOptions": {
        "tsConfigPath": "apps/admin-app/tsconfig.app.json"
      }
    },
+    "my-core": {
+      "type": "library",
+      "root": "libs/my-core",
+      "entryFile": "index",
+      "sourceRoot": "libs/my-core/src",
+      "compilerOptions": {
+        "tsConfigPath": "libs/my-core/tsconfig.lib.json"
+      }
+    }
  }
}

作成したmy-coreをアプリから利用する場合はこんな感じです

libs/my-core/src/index.ts
export * from './my-core.module';
export * from './my-core.service';

+ export const hello = 'hello monorepo';
apps/my-app/src/app.service.ts
+ import { hello } from '@my-core/my-core';
import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getHello(): string {
+    return hello;
-    return "Hello World!"
  }
}

library作成時に自動でimportプレフィックスも作成してくれているのもいいですね👍
そしてもちろんそれぞれ別個にbuildが可能です

npx nest build my-app
npx nest build admin-app
npx nest build my-core
 dist
  ├── apps
  │    ├── admin-app
  │    └── my-app
  └── libs
       └── my-core

参考

https://docs.nestjs.com/
https://docs.nestjs.com/cli/monorepo
https://docs.nestjs.com/cli/libraries

Discussion