scaffolding-nestjs-app について
空のディレクトリや既存のモノレポで NestJS サービスを初期構築するスキルです。ログ、API 動作、テスト、デプロイの基準を整え、環境に応じた構成選択を案内します。
主な機能
- 01Nest CLIとNx環境の判別
- 02Pinoロギングの基準設定
- 03統一APIレスポンスと例外処理
- 04カバレッジ付きVitest設定
- 05Docker、Compose、Makefileの土台
活用シーン
- 01NestJSサービスの新規作成
- 02NxモノレポへのNestJSアプリ追加
- 03テストとデプロイの基盤構築
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ satanlittlehelper/ ai-dev-kit --skill scaffolding-nestjs-appインストール後、Claude Code で/scaffolding-nestjs-appを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
templates/docker/.env.example.standalonedocker-compose-service-snippet.monorepo.mddocker-compose.standalone.ymldockerfile-monorepo-reference.mdDockerfile.standaloneenv-example-snippet.monorepo.mdMakefile.monorepo-referenceMakefile.standalonetemplates/docs/claude-md-nestjs-snippet.mdclaude-md-skills-workflow-snippet.mdgitignore-ai-artifacts-snippet.txttemplates/eslint/.prettierignore.prettierrceslint.app.config.mjseslint.base.config.mjstemplates/husky/lintstagedrc.nx.jslintstagedrc.standalone.jspre-commitpre-push.nxpre-push.standalonetemplates/pino/app-logger.module.tspino-config.constants.tspino-config.helpers.tspino-config.loader.tspino-config.types.tstemplates/response/api-exception-filter.tsapi-response.interceptor.tsapi.types.tstemplates/vitest/vitest.config.tsvitest.setup.ts詳細
新しい NestJS アプリをスキャフォールディングする
概要
空のディレクトリ(または既存の Nx モノレポ内の新しいアプリ)を、作成者標準のベースラインを備えた NestJS サービスに変換します。ベースラインには、pino ロギング、統一された ApiResponse 契約とグローバル例外フィルター、ESLint/Prettier、カバレッジ付き Vitest、husky のコミット前/プッシュ前フック、Docker/Compose/Makefile のデプロイスキャフォールディングが含まれます。テンプレートはこのファイルと同じ場所の templates/ にあります。記憶を頼りに書き直すのではなく、コピーして調整してください。
使用する場面
- ユーザーから、NestJS アプリ/サービス/マイクロサービスをゼロから作成するよう依頼された場合。
- ユーザーから、既存の Nx(またはその他の)モノレポに新しいバックエンドアプリを追加するよう依頼された場合。
- すでにスキャフォールディング済みのアプリに単一の機能/モジュールを追加する場合には使用しないでください。このスキルの対象は初期ブートストラップのみです。
ステップ 0 — 環境を検出する
digraph detect_env {
"nx.json exists in target repo root?" [shape=diamond];
"Any existing app/project in the repo?" [shape=diamond];
"Use Nx generator" [shape=box];
"Ask user: fresh Nx workspace or plain Nest CLI app?" [shape=diamond];
"Use Nest CLI (nest new / nest generate)" [shape=box];
"npx create-nx-workspace, then Nx generator" [shape=box];
"nx.json exists in target repo root?" -> "Use Nx generator" [label="yes"];
"nx.json exists in target repo root?" -> "Any existing app/project in the repo?" [label="no"];
"Any existing app/project in the repo?" -> "Use Nest CLI (nest new / nest generate)" [label="yes"];
"Any existing app/project in the repo?" -> "Ask user: fresh Nx workspace or plain Nest CLI app?" [label="no"];
"Ask user: fresh Nx workspace or plain Nest CLI app?" -> "npx create-nx-workspace, then Nx generator" [label="Nx"];
"Ask user: fresh Nx workspace or plain Nest CLI app?" -> "Use Nest CLI (nest new / nest generate)" [label="plain Nest"];
}
nx.jsonがある場合 →npx nx g @nx/nest:application apps/<app-name>(アプリ名/パスが指定されていない場合は確認してください。プラグインがまだインストールされていない場合は、先に@nx/nestを devDependencies に追加してください)。nx.jsonはないが、リポジトリにすでに少なくとも 1 つのアプリ/パッケージがある場合 → Nx 以外の複数アプリリポジトリです。既存アプリの構成に従って、nest new <app-name>またはnest generateを使用してください。Nx を使用していないリポジトリに、黙って Nx を導入しないでください。nx.jsonがなく、既存のアプリもまったくない場合(空/新規リポジトリ) → これは「プロジェクトで最初のアプリ」のケースです。Nx ワークスペース(npx create-nx-workspace@latestを実行後、上記の Nx ジェネレーターを使用)をセットアップするか、プレーンなスタンドアロン Nest CLI アプリ(npx @nestjs/cli new .)にするかを、必ずユーザーに確認してください。どちらかを決めつけないでください。この選択によって、その後のすべての手順(使用するテンプレートの種類:*.standalone.*と Nx/モノレポ用のもの)が決まります。
ステップ 1 — 依存関係をインストールする
このスキルに必要な一覧です。最新バージョン(@latest)をインストールし、手動でバージョンを固定しないでください。モノレポでは、まずワークスペースルートの package.json を確認し、実際に不足しているものだけを追加してください。ESLint/Vitest ツールの多くは、通常すでに含まれています。
依存関係:
npm install nestjs-pino pino pino-http class-validator class-transformer
例外フィルターの axios エラー分岐を使用する場合は axios も追加してください(ステップ 5 を参照)。それ以外の場合は追加しないでください。
開発依存関係:
npm install -D pino-pretty eslint prettier eslint-config-prettier eslint-plugin-import eslint-plugin-prettier typescript-eslint vitest @vitest/coverage-v8 unplugin-swc vite-tsconfig-paths husky lint-staged
モノレポの場合のみ、ワークスペースの依存関係にまだ含まれていなければ @nx/eslint-plugin も追加してください。
ステップ 2 — 生成後のクリーンアップ:TypeScript の厳格モード、Jest と e2e のひな型を削除
このスタックのすべてのアプリでは、厳格モードがデフォルトです。ジェネレーターが設定したと思い込まず、必ず確認してください。 アプリの tsconfig.json(単独構成の場合)またはワークスペースの tsconfig.base.json(モノレポの場合)を開き、"compilerOptions"."strict": true が設定されていることを確認します。nest new のデフォルトテンプレートではすでに設定されていますが、@nx/nest:application はワークスペースの tsconfig.base.json を継承します。この設定ファイルがこの規約より前に作られている場合があるため、設定がなければ明示的に追加してください。ひな型の作成中に型エラーを回避するため、個別の厳格モード用フラグ(strictNullChecks、noImplicitAny など)を無効にしないでください。根本原因となっている型を修正してください。
nest new と @nx/nest:application は、どちらもデフォルトで Jest をひな型として生成します。この筆者のスタックで使うのは Vitest のみで、独立した e2e テストターゲットはありません。アプリの生成直後、ほかの作業に手を付ける前に、次のファイルや設定を削除してください。
jest.config.ts/jest.config.js(アプリレベルのもの、および新規の単独リポジトリではルートのものも)test/(生成された e2e フォルダー。通常はapp.e2e-spec.tsとjest-e2e.jsonが含まれます)- ソースと一緒に Jest が生成した
*.spec.tsファイル(例:src/app.controller.spec.ts)。Vitest に移植せず、削除してください。実際にテスト対象となるロジックができたら、このプロジェクト独自のテスト規約に従って、後から実際の spec ファイルを作成します。 - Jest 関連の devDependencies:
jest、@nestjs/testing(ほかの用途がなければ。ここでは使用しません)、ts-jest、@types/jest、jest-environment-node(ジェネレーターによって追加されるものが正確に異なるため、package.jsonを確認してください。Nx/Nest のジェネレーターのバージョンによって具体的な一覧は異なるので、追加されたものをすべて削除します)。 package.jsonに埋め込まれたjest設定ブロック(Nx の@nx/nest:applicationと通常のnest newは、どちらもトップレベルに"jest"キーを追加します)、および Jest を指定するtest/test:e2e/test:watch/test:covnpm スクリプト。これらはステップ 7 で Vitest 相当の設定に置き換えます。- Nx モノレポのアプリの場合:
@nx/jest:jestを参照する、生成されたproject.jsonのtest/e2eターゲットも削除します。ステップ 8 で、ワークスペース内のほかのアプリに合わせて、これらをnx:run-commandsとvitest runに置き換えます。
Vitest/tsc の設定:spec ファイルによって本番ビルドや IDE が失敗しないようにします。
tsconfig.app.jsonの"include"の隣に"exclude": ["src/**/*.spec.ts", "src/**/*.test.ts"]を追加します。これを追加しないと、本番ビルドが spec ファイルを取り込み、viなど Vitest 専用のグローバルが原因で失敗します。tsconfig.spec.jsonを作成します:{ "extends": "./tsconfig.json", "compilerOptions": { "types": ["vitest/globals"] }, "include": ["src/**/*.spec.ts", "src/**/*.test.ts", "vitest.config.ts"] }。これがないと、Vitest 自体は問題なく実行できても、spec ファイル内のCannot find name 'vi'が IDE とtscに報告されます。- すべての spec ファイルで、グローバルのみに依存せず、
'vitest'からviを明示的にインポートします。IDE がtsconfig.spec.jsonの型を解決するのは、実際にその設定を使っている場合に限られます。明示的にインポートすれば、設定にかかわらず動作します。
アプリの基本設定:設定、グローバルプレフィックス、バリデーション
ステップ 3 の前に、ジェネレーターの出力にかかわらず、main.ts/app.module.ts が次の基本設定を備えていることを確認します。グローバルな ConfigModule で PORT を検証すること(Joi または class-validator を使用し、使用箇所では getOrThrow を使います。単独の process.env.PORT は使用しません)、app.setGlobalPrefix('api')、transform: true と whitelist: true を設定し、暗黙的な変換を有効にしたグローバルな ValidationPipe、および class-validator のカスタムデコレーターで Nest DI を使えるようにする useContainer(app, { fallbackOnErrors: true }) です。アプリが、サービス名のプレフィックスを取り除いてから転送する Traefik(または同等のリバースプロキシ)の背後にある場合、アプリ独自のルートは通常の /api/... のままにします。プロキシのルートパターン(例:ストリッププレフィックスのミドルウェアを使う /{service_name}/api)はプロキシ側の責任であり、アプリ内部で把握する必要はありません。
アプリの基本設定:プロセスガード
各アプリの main.ts は、リクエストチェーンの外で発生する障害にも耐えられる必要があります。これがないと、未処理の拒否や例外によって、pino を経由せず生のスタックトレースが stderr に出力されてプロセスが終了します。また、SIGTERM では Nest のシャットダウンフックが実行されないため、Redis/BullMQ/DB の接続が閉じられません。共有ヘルパーから取得する次の2つの関数を呼び出します。モノレポでは @chatbot-platform/helpers/nest の registerProcessGuards/runBootstrap を使い(重複して実装しないでください)、スタンドアロンアプリでは同じ2つの関数を src/common/process-guards/ に作成します。main.ts に process.on(...) を直接記述してはいけません。
runBootstrap(bootstrap)は、main.tsの末尾にある単独のbootstrap()/void bootstrap()呼び出しを置き換えます。bootstrap が拒否された場合はconsole.errorに出力され(pino の準備がまだできていない可能性があります)、プロセスは終了コード 1 で終了します。registerProcessGuards({ app, logger: app.get(Logger) })は、app.useLogger(app.get(Logger))の直後に呼び出します。app.enableShutdownHooks()を実行し、unhandledRejection/uncaughtExceptionが発生したときは pino を通じてログを記録し、名前付き定数で指定したタイムアウトと競合させながらapp.close()を実行した後、process.exit(1)を呼び出します。シャットダウン中に繰り返し発生したイベントは無視されます。どちらのイベントも同じように処理します。コンテナの再起動ポリシーによってサービスは復旧しますが、「稼働中だが壊れている」プロセスを残すより、再起動するほうが適切です。
アプリで @nestjs-modules/ioredis または BullMQ の @Processor を登録する場合は、pino を通じてログを記録する error リスナーも追加します(プロセッサーには @OnWorkerEvent('error')、または @InjectRedis() を使って redis.on('error', ...) を呼び出す小さなモジュールを使用します)。そうしないと、ioredis と BullMQ のエラーは pino を経由せず、console.error に出力されます。
ステップ 3 — API バージョニングについて確認する
main.ts を作成する前に、新しいアプリで URI API バージョニングが必要かどうかを必ずユーザーに確認してください。どちらかに決めつけてはいけません。このスタックでは有効にしているアプリ(ext-gate)もあれば、有効にしていないアプリ(bot-gateway、miniapp-gate、bot-publisher)もあります。これはアプリごとの判断であり、一律の規約ではありません。
必要な場合は、main.ts に追加します(setGlobalPrefix の後、useContainer の前)。
import { VersioningType } from '@nestjs/common';
app.enableVersioning({
type: VersioningType.URI,
defaultVersion: '1',
});
これにより、ルートは /api/v1/... として解決されます。不要な場合は追加せず、ルートは /api/... のままにします。
ステップ 4 — pino ロガーを組み込む
templates/pino/ ディレクトリ全体を新しいアプリにコピーします。これは相互に連携する5つのファイルであり、単一のモノリシックなモジュールではありません。
| テンプレートファイル | 役割 |
|---|---|
pino-config.types.ts | AppLoggerOptions、AppLoggerContextFilter、PinoConfig と、それぞれの is* 型ガード |
pino-config.constants.ts | REDACT_AUTH_HEADER_PATHS(認証/トークンのヘッダーをデフォルトで秘匿)、PINO_CONFIG_FILENAME |
pino-config.helpers.ts | buildPinoParams — pretty-transport、秘匿するパス、コンテキストのブラックリストフックを構築する純粋なビルダー |
pino-config.loader.ts | loadPinoConfig — PINO_CONFIG_ENABLED=true の場合、cwd から省略可能な pino.config.json を読み込みます |
app-logger.module.ts | 上記4つを組み合わせる薄い nestjs-pino ラッパー、AppLoggerModule.forRoot/forRootAsync |
配置場所:
- すでに複数の Nest アプリでコードを共有しているモノレポ → 共有ライブラリがまだなければ、ディレクトリ全体を共有ライブラリ(例:
libs/nest-logger)に配置します。すでにある場合は、それを依存先として使用し、重複して実装しないでください。 - スタンドアロンアプリ、またはモノレポの最初のアプリ → アプリ内の
src/common/logger/に、同じ5つのファイルを配置します。 各オプションの制御内容(buildPinoParamsが受け取るAppLoggerOptions): level— pino のログレベル('info'、'debug'、…)。pretty— 開発モードのpino-prettyトランスポート(colorize: true、translateTime: 'SYS:standard')。本番環境ではオフです。— NDJSONは下流のログ転送ツールにそのまま渡され、色付けや整形された出力はノイズになるだけです。singleLine—pino-pretty独自のsingleLineオプションにそのまま渡されます。trueなら、大きなreq/resペイロードがあってもリクエストごとにログ1行を維持し、falseなら、深くネストされたオブジェクトをローカルで読みやすいように複数行で整形します。pinoConfigEnabled—trueの場合、loadPinoConfigはprocess.cwd()にある、{ contextFilter?: { blacklist: string[] }, redactPaths?: string[] }という形式のpino.config.jsonを必須とし、ファイルが存在しないか形式が不正であれば、説明付きのエラーをスローします。false(デフォルト)の場合、ファイルは読み込まれず、組み込みの秘匿対象パスのみが適用されます。共有ライブラリをプロジェクトごとに手作業で編集するのではなく、この回避手段を使って、プロジェクト固有のノイズが多いケース(ヘルスチェックモジュール独自のcontextが開発ログを埋め尽くす場合や、組み込みの秘匿対象リストでカバーされないフィールドなど)に対応してください。
LOG_LEVEL/LOG_PRETTY/LOG_PRETTY_SINGLE_LINE/PINO_CONFIG_ENABLEDには、4つすべてで同じJoiスキーマ形式を選び、一貫して使用してください。 どちらの形式も機能します。問題は混在させることです。
- 文字列形式(
Joi.string().empty('').valid('true', 'false').default('true')。config.get<string>('LOG_PRETTY') === 'true'として比較)— このスキルのデフォルト形式です。同じ環境変数の値をConfigServiceの外部(たとえばシェルスクリプトへの出力)でも読み取る可能性がある場合に必要です。ネイティブのブール値はシリアライズ後に保持できないためです。 - ブール値形式(
Joi.boolean().default(false)。config.getOrThrow<boolean>('LOG_PRETTY')として読み取り)— Nest独自のConfigService以外で生の値を必要としない場合は、こちらのほうが簡潔です。
どちらの形式を選ぶ場合も、4つのキーすべてに同じ形式を適用してください。 混在させることこそ、この警告がある理由となる実際の不具合です。LOG_PRETTY: Joi.boolean()を宣言しながら、config.get<string>('LOG_PRETTY') === 'true'として比較すると、検証エラーは発生せず、ログの出力が誤った状態で、結果は暗黙的にfalseになります。実際のbooleanは文字列'true'と===にならないためです。キーごとに、スキーマで宣言した型とファクトリーでの読み取り方法を一致させてください。
文字列形式の例:
LOG_LEVEL: Joi.string().default('info'),
LOG_PRETTY: Joi.string().empty('').valid('true', 'false').default('true'),
LOG_PRETTY_SINGLE_LINE: Joi.string().empty('').valid('true', 'false').default('true'),
PINO_CONFIG_ENABLED: Joi.string().empty('').valid('true', 'false').default('false'),
app.module.tsに組み込みます(文字列形式のファクトリーです。ブール値形式を使う場合は、すべての=== 'true'/config.get<string>をconfig.getOrThrow<boolean>に置き換えてください):
AppLoggerModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService): AppLoggerOptions => ({
level: config.getOrThrow<string>('LOG_LEVEL'),
pretty: config.get<string>('LOG_PRETTY') === 'true',
singleLine: config.get<string>('LOG_PRETTY_SINGLE_LINE') === 'true',
pinoConfigEnabled: config.get<string>('PINO_CONFIG_ENABLED') === 'true',
}),
}),
また、main.tsでは、NestFactory.create(AppModule, { bufferLogs: true })を実行し、次にapp.useLogger(app.get(Logger))(nestjs-pinoのLogger)を呼び出してから、registerProcessGuards({ app, logger: app.get(Logger) })を呼び出します(「App Baseline: Process Guards」を参照)。
環境変数 — このリポジトリ独自の環境変数に関する規約に従い、3か所すべてを更新してください(上記のJoiスキーマ、ルートの.env.example、そしてdevops/デプロイ層があるモノレポの場合は、devops/.env.exampleと対応するdocker-compose.prod.ymlサービスの環境変数ブロック):LOG_LEVEL、LOG_PRETTY、LOG_PRETTY_SINGLE_LINE、PINO_CONFIG_ENABLED。いずれかを変更する特別な理由がない限り、すべての箇所でLOG_PRETTY_SINGLE_LINE=true、PINO_CONFIG_ENABLED=falseをデフォルトにしてください。
組み込み後は、実際にアプリを起動し(.envでLOG_PRETTY=trueを設定)、色付きの1行出力が表示されることを確認してから先に進んでください。生のNDJSONではありません。prettyフラグが暗黙的にfalseになっていても、そうしないと見落としやすいためです。
Step 5 — 例外フィルターと API レスポンスインターセプターを接続する
まず、重複がないか確認します(この手順は最も省略されがちです)。新しいものを作成する前に、対象のリポジトリを検索し、既存の
ExceptionFilter/レスポンスインターセプターの実装がないか確認してください。
- 見つかった場合で、リポジトリがモノレポの場合 → 共有ライブラリに切り出します(このスキル自体の履歴を参考にしてください。まさにこの切り出しを行った例として、
@chatbot-platform/helpers/nestを参照)。既存のアプリと新しいアプリの両方が、共有バージョンに依存するようにします。異なる実装のコピーを2つ残さないでください。 - 見つかった場合で、リポジトリがスタンドアロンの場合 → そのまま再利用し、2つ目を作成しないでください。
- 見つからない場合 →
templates/response/api.types.ts、api-exception-filter.ts、api-response.interceptor.tsをコピーします。ファイルの配置先に合わせて、../types/api.typesのインポートパスを修正してください。アプリで axios を使って他の HTTP API を呼び出す理由がない場合は、フィルターからisAxiosError分岐とaxiosのインポートを削除してください。
app.module.ts のプロバイダーに登録します。main.ts の useGlobalFilters/useGlobalInterceptors は使わないでください。また、他の APP_INTERCEPTOR エントリーがある場合、ApiResponseInterceptor を最後に配置して、それらの出力をラップするようにします。
providers: [
{ provide: APP_INTERCEPTOR, useClass: ApiResponseInterceptor },
{ provide: APP_FILTER, useClass: ApiExceptionFilter },
],
Step 6 — ヘルスエンドポイントを接続する
このスタックで新しく作るすべてのアプリに、依存関係のないシンプルな稼働確認エンドポイントを追加します。ユーザーから明示的な依頼がない限り、@nestjs/terminus や DB/Redis の準備状態チェックは使いません。実装パターン(テンプレートファイルからコピーせず、インラインで記述します。直接書けるほど小さいためです):
src/app/app.controller.ts:
import { Controller, Get } from '@nestjs/common';
import { Public } from '<path-to-shared-public-decorator-or-local-equivalent>';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Public()
@Get('health')
getHealth(): { service: string; status: string } {
return this.appService.getHealth();
}
}
src/app/app.service.ts(または、テスト可能なサービスに対してアプリですでにドット記法のサブフォルダー規約を使用している場合は src/app/app.service/app.service.ts):
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHealth(): { service: string; status: string } {
return {
service: '<app-name>',
status: 'ok',
};
}
}
app.module.ts に両方を登録します。controllers: [AppController] を指定し、providers に AppService を追加してください。Public デコレーターを付けると、そのルートはグローバル認証ガード(APP_GUARD)によるチェックをスキップします。アプリのガードが Reflector/メタデータを使ったパブリックルートのバイパスにまだ対応していない場合は、まずガードにその処理を追加してください(認証ロジックの前に Reflector.getAllAndOverride を確認する箇所については、ガード自身の canActivate を参照してください)。結果のパスは /api/health です。このアプリで Step 3(バージョニング)を有効にした場合は /api/v1/health になります。
Step 7 — ESLint + Prettier
templates/eslint/eslint.base.config.mjs をリポジトリのルートにコピーします(既存の設定がある場合は上書きせず、統合してください。カスタマイズされたベース設定を上書きしないでください)。スタンドアロン(非 Nx)アプリでは、@nx/enforce-module-boundaries ブロックと、apps/*/libs/* の tsconfig glob パスを削除します。削除対象の詳細は、テンプレート内のコメントを参照してください。
モノレポのアプリでは、templates/eslint/eslint.app.config.mjs もコピーし、__APP_NAME__ を実際のアプリ名に置き換えて、apps/<app-name>/eslint.config.mjs として配置します。
templates/eslint/.prettierrc と templates/eslint/.prettierignore をリポジトリのルートにコピーします(両方ともすでに存在する場合はスキップし、カスタマイズされた設定を上書きしないでください)。eslint.base.config.mjs は prettier/prettier: 'error' を強制するため、.prettierrc と ESLint ルールのフォーマットが一致しないと、lint が失敗します。
Step 8 — Vitest
templates/vitest/vitest.config.ts と templates/vitest/vitest.setup.ts を新しいアプリにコピーします(vitest.config.ts はアプリのルートに、src/test/vitest.setup.ts はそのパスに配置します)。coverage.exclude のリストはそのまま残してください。これは、*.api.service.ts、*.repository.ts、DTO、モジュール、コントローラー、例外フィルターを単体テストの対象外とする、作成者の規約を反映しています(ロジックを含まない接着層、または間接的にすでにカバーされているものです)。まだテストがない新しいアプリでは、thresholds はコメントアウトしたままにしてください。プロジェクトに十分なカバレッジができたらコメントを解除し、カバレッジ 80% のしきい値によって最初のコミットを妨げないようにします。
ステップ 9 — pre-commit/pre-push フック
.husky/ がまだ存在しない場合は、npx husky init を実行します。以下をコピーします。
templates/husky/pre-commit→.husky/pre-commit(どちらの構成でも同じです)。- モノレポ:
templates/husky/pre-push.nx→.husky/pre-push、templates/husky/lintstagedrc.nx.js→.lintstagedrc.js(ルートに配置し、全アプリで共有します。アプリごとに複製しないでください)。 - スタンドアロン:
templates/husky/pre-push.standalone→.husky/pre-push、templates/husky/lintstagedrc.standalone.js→.lintstagedrc.js。
両方のフックファイルに実行権限を付与します(chmod +x .husky/pre-commit .husky/pre-push)。
ステップ 10 — Docker/docker-compose/Makefile/.env.example
- スタンドアロンアプリ:
templates/docker/Dockerfile.standalone→devops/Dockerfile、templates/docker/docker-compose.standalone.yml→devops/docker-compose.yml、templates/docker/Makefile.standalone→Makefile、templates/docker/.env.example.standalone→.env.example。 - 共有パラメーター化 Dockerfile(
ARG APP_NAME)がすでにあるモノレポ →
templates/docker/dockerfile-monorepo-reference.mdを読み、既存のdevops/Dockerfileがその形式に一致することを確認します。新しい Dockerfile は作成せず、templates/docker/docker-compose-service-snippet.monorepo.mdをひな形としてサービスブロックだけを追加し(__APP_NAME__/__APP_NAME_UPPER__プレースホルダーを置き換え)、templates/docker/env-example-snippet.monorepo.mdに従って新しい環境変数を追加します。既存のルートMakefileは変更せずに再利用します。 - 最初のアプリで、まだ devops/ の設定がないモノレポ →
templates/docker/dockerfile-monorepo-reference.mdの参照ブロックからdevops/Dockerfileを作成し、templates/docker/Makefile.monorepo-referenceからルートのMakefileを作成します(__PROJECT_NAME__をリポジトリ名に置き換えます)。この Makefile のprod-*/dev0-*ターゲットはdocker-compose.networks.brandnew.yml/.existing.ymlとdocker-compose.base.ymlを参照します。これらは、ソースプロジェクト固有の新規/既存 Docker ネットワークのデプロイトポロジーを表すため、新しいリポジトリでも同じ分岐が必要な場合にのみ追加してください。それ以外の場合は、_BNN/_EXN変数を削除し、COMPOSE_PRODターゲットがdocker-compose.prod.ymlを直接参照するようにします。
ステップ 11 — モノレポ:ルートの package.json に start スクリプトを追加する
モノレポのみ(スタンドアロンアプリではスキップします。npm run start がすでにアプリ独自のエントリーポイントです)。ワークスペースのルート package.json に start:<app-name> スクリプトを追加し、既存の規則に合わせます(例:
"start:ext-gate": "nx run ext-gate:serve")。これがないと、新しいアプリには、ほかのアプリの start:* スクリプトと並ぶ、ローカル実行用の説明済みエントリーポイントがありません。
ステップ 12 — 対象リポジトリに規約を記載する
templates/docs/claude-md-nestjs-snippet.md の内容を(__APP_NAME__ を置き換え、パスを調整して)対象リポジトリの
CLAUDE.md および/または AGENTS.md に、既存の規約の隣に追記します。どちらのファイルもまだない場合は、リポジトリのルートに CLAUDE.md を作成し、これを初期内容として使用します。
また、templates/docs/claude-md-skills-workflow-snippet.md も追記します。これはスタックに依存しない内容(スキル/サブエージェントの使い方、プランモード、コミットの規律、ワークフロー成果物の保存場所)であり、NestJS プロジェクトだけでなく、すべてのプロジェクトの CLAUDE.md に追加します。対象リポジトリの CLAUDE.md に同等のスキル/ワークフロー規約がすでに記載されている場合のみスキップしてください。重複させないでください。
ステップ 13 — .gitignore に AI の成果物を追加する
templates/docs/gitignore-ai-artifacts-snippet.txt を対象リポジトリの .gitignore に追記します(ファイルがない場合は作成します)。このスニペットで無視するのは、個人用の Claude Code ファイル(CLAUDE.local.md、settings.local.json、worktrees、.doc-review/)のみです。ステップ 11 で追加した CLAUDE.md/AGENTS.md はプロジェクト共有ファイルなので、引き続き追跡対象にします。追記前に既存の記述を確認し、すでにある行は重複させないでください。
クイックリファレンス — テンプレートファイル
| ファイル | 目的 | 単独構成の場合のみ必要な編集 |
|---|---|---|
templates/pino/app-logger.module.ts | nestjs-pino のラッパーモジュール(以下の4ファイルを組み合わせます) | なし |
templates/pino/pino-config.types.ts | AppLoggerOptions/PinoConfig の型と型ガード | なし |
templates/pino/pino-config.constants.ts | デフォルトの redact パス、pino.config.json のファイル名 | なし |
templates/pino/pino-config.helpers.ts | buildPinoParams の純粋なビルダー | なし |
templates/pino/pino-config.loader.ts | オプションの pino.config.json ローダー | なし |
templates/response/api.types.ts | ApiResponse<T> の契約 | なし |
templates/response/api-exception-filter.ts | グローバルな @Catch() フィルター | 未使用なら axios 分岐を削除 |
templates/response/api-response.interceptor.ts | コントローラーの戻り値をラップ | なし |
templates/eslint/eslint.base.config.mjs | ルートのフラット ESLint 設定 | Nx の境界ルールを削除し、tsconfig パスを glob 化 |
templates/eslint/eslint.app.config.mjs | アプリごとの ESLint オーバーライド | モノレポのみ |
templates/eslint/.prettierrc + .prettierignore | Prettier のフォーマットルール | なし |
templates/vitest/vitest.config.ts + vitest.setup.ts | テストランナーとカバレッジ | なし |
templates/husky/pre-commit | lint-staged フック | なし |
templates/husky/pre-push.nx / pre-push.standalone | push 時のテストフック | どちらかを選択 |
templates/husky/lintstagedrc.nx.js / lintstagedrc.standalone.js | lint-staged の設定 | どちらかを選択 |
templates/docker/Dockerfile.standalone / dockerfile-monorepo-reference.md | マルチステージビルド | どちらかを選択 |
templates/docker/docker-compose.standalone.yml / docker-compose-service-snippet.monorepo.md | デプロイ設定 | どちらかを選択 |
templates/docker/Makefile.standalone / Makefile.monorepo-reference | local-up/down/clean ターゲット | どちらかを選択 |
templates/docker/.env.example.standalone / env-example-snippet.monorepo.md | 環境変数のベースライン | どちらかを選択 |
templates/docs/claude-md-nestjs-snippet.md | 対象リポジトリ向けの NestJS 固有の規約をまとめたもの | パスを調整 |
templates/docs/claude-md-skills-workflow-snippet.md | スタックに依存しないスキル/サブエージェントのワークフロールール | なし |
templates/docs/gitignore-ai-artifacts-snippet.txt | 個人用の Claude Code ファイル(CLAUDE.local.md、settings.local.json、worktrees)を除外 | なし |
よくある間違い
| 間違い | 修正方法 |
|---|---|
main.ts で app.useGlobalFilters() を使ってフィルター/インターセプターを登録する | 代わりに app.module.ts の APP_FILTER/APP_INTERCEPTOR プロバイダー経由で登録します |
ApiResponseInterceptor を他の APP_INTERCEPTOR より前に置く | 他のインターセプターの出力をラップするため、最後に置く必要があります |
| モノレポに既存の例外フィルター/インターセプターがあるのに、別のものを作成する | 既存のものを共有ライブラリに切り出して再利用します |
Nx の @nx/enforce-module-boundaries ESLint ルールを単独構成のアプリにコピーする | 削除します。適用すべき libs/apps の境界はありません |
| 初日からカバレッジのしきい値を厳しく設定する | アプリに実際のテストが追加されるまで、thresholds はコメントアウトしたままにします |
| すでに共有のパラメーター化された Dockerfile があるモノレポで、新しいアプリごとの Dockerfile を作成する | ARG APP_NAME を使う共有の devops/Dockerfile を再利用し、compose のサービスブロックだけを追加します |
| 必須の3か所のうち1か所だけに新しい環境変数を追加する | Joi スキーマ + ルート .env.example + devops/.env.example(+ compose の environment: ブロック)に毎回すべて追加します |
registerProcessGuards を使わず、main.ts を単独の bootstrap() / void bootstrap() で終える | runBootstrap(bootstrap) でラップし、useLogger の直後に registerProcessGuards を呼び出します。「アプリのベースライン:プロセスガード」を参照してください |
| アプリが「内部向け」だからとヘルスエンドポイントを省略する | すべての HTTP アプリに GET /health(グローバルプレフィックスがある場合は /api/health)を設けます。内部専用アプリでも、オーケストレーションの稼働確認に必要です |