NestJSはアーキテクチャが明確に定義されているフレームワークだ。モジュール、コントローラー、サービス、ガード、インターセプター——この構造は、Claude Codeと組み合わせたときにむしろ強みになる。規約が明確なほど、AIエージェントはコードベースを正確にナビゲートできる。
ただし、それはCLAUDE.mdでNestJS固有のルールを定義した場合に限る。
汎用的なTypeScript向けCLAUDE.mdでも動くコードは生成できる。でもNestJS専用のCLAUDE.mdがあれば、モジュール境界に沿ったコードを生成し、DIコンテナを正しく扱い、最初のテストから通るコードを出力できる。その差は実務で体感できるレベルに大きい。
実際のNestJSプロジェクトでClaude Codeを使ったパターンを分析して、フィードバックループを短くする設定をまとめた。CLAUDE.mdテンプレート、AGENTS.mdのマルチエージェント設定、よくある失敗パターン、NestJS特有のテスト設計まで解説する。
実際のCLAUDE.mdやAGENTS.mdのサンプルはルールコレクションでも公開している。
なぜNestJSには専用のCLAUDE.mdが必要か
フレームワーク固有の指示がなければ、Claude CodeはNestJSを汎用TypeScriptプロジェクトとして扱う。結果は予測できる:
- サービスがコントローラーをインポートする(依存関係の逆転)
process.env.DATABASE_URLがサービスファイルに直接書かれる(ConfigServiceを経由すべき)- DTOがinterfaceとして定義される(
class-validatorはクラスインスタンスにのみ機能する) - 内部で使うべきサービスがモジュールから
exportされる - DIスコープを壊すプロパティインジェクションがコンストラクタインジェクションの代わりに使われる
- テストで
TestingModuleを使わずにモジュール全体をモックする
どれも原理的に間違いではない。NestJSとしては間違いなだけで、修正コストのほうが最初にルールを書くコストより高い。
CLAUDE.mdテンプレート
プロジェクトのCLAUDE.mdにそのままコピーして使える。セクションはミス発生頻度の高い順に並んでいる——最初のいくつかのルールだけで、摩擦の大半は防げる。
# CLAUDE.md — NestJSプロジェクト
## アーキテクチャルール
- **モジュール境界**: 別モジュールのサービスをインポートするのは、そのモジュールの`@Module()`デコレーターが明示的に`exports: []`に追加している場合のみ。必要なら`exports`に追加すること。
- **依存の方向**: サービスはリポジトリ/外部サービスに依存する。コントローラーはサービスに依存する。サービスがコントローラーをインポートすることは禁止。
- **コンストラクタインジェクションのみ使用**: すべての依存関係はコンストラクタインジェクションを使う。`@Inject()`によるプロパティインジェクションは、`@Optional()`を使う省略可能な依存関係を除いて禁止。
- **ConfigService必須**: サービスやコントローラーで`process.env`に直接アクセスしない。必ず`ConfigService`をインジェクトして`this.configService.get<string>('KEY')`で取得する。
- **プロバイダーはデフォルトでシングルトン**: そのように設計する。リクエストスコープのプロバイダーが必要な場合は`scope: Scope.REQUEST`を明示的に設定し、理由をコメントで記載する。
## ファイルと命名規則
- モジュール: `{feature}.module.ts`
- コントローラー: `{feature}.controller.ts` — 1リソース1コントローラー
- サービス: `{feature}.service.ts`
- リポジトリ: `{feature}.repository.ts`(カスタムリポジトリを使う場合)
- DTO: `create-{feature}.dto.ts`、`update-{feature}.dto.ts`
- ガード: `{name}.guard.ts` — クラス名は`{Name}Guard`
- インターセプター: `{name}.interceptor.ts` — クラス名は`{Name}Interceptor`
- パイプ: `{name}.pipe.ts` — クラス名は`{Name}Pipe`
- 例外フィルター: `{name}.filter.ts` — クラス名は`{Name}Filter`
## DTOとバリデーション
- DTOはinterfaceではなくclassで定義する。`class-validator`のデコレーターはクラスインスタンスにのみ機能する。
- バリデーションが必要なDTOのすべてのプロパティには、最低1つの`class-validator`デコレーターを付ける。
- オプションフィールドには`@IsOptional()`を使う。`?`だけでは不十分。
- ネストしたオブジェクトの変換には`class-transformer`の`@Type(() => SubDto)`を使う。
- グローバルの`ValidationPipe`では`whitelist: true`と`forbidNonWhitelisted: true`を有効にする。
```typescript
// 正しいDTOパターン
import { IsString, IsEmail, IsOptional, MinLength } from 'class-validator';
export class CreateUserDto {
@IsString()
@MinLength(2)
name: string;
@IsEmail()
email: string;
@IsOptional()
@IsString()
bio?: string;
}
ORM統合
TypeORMを使う場合
- エンティティファイルは
src/{feature}/entities/{feature}.entity.tsに配置する - サービスのコンストラクターで
@InjectRepository(Entity)デコレーターを使う - ビジネスロジックで
dataSource.query()を直接呼び出さない——リポジトリパターンを使う - マイグレーションは
src/migrations/に置く——本番環境ではsynchronize: true禁止
Prismaを使う場合
PrismaServiceは共有モジュールのプロバイダー——新規インスタンスを作らずPrismaModuleをインポートする- 一連の操作が成功か失敗かで揃う必要がある場合は
prisma.$transaction()を使う prisma.$queryRawによる生SQLは複雑な集計クエリにのみ許可
テスト
- ユニットテスト:
@nestjs/testingのTest.createTestingModule()を使う。モジュール全体をモックせず、直接の依存関係のみモックする。 - 統合テスト: フルの
NestApplicationインスタンスでsupertestを使う。 - テストファイル: ユニットテストは
{name}.spec.ts、e2eテストは{name}.e2e-spec.ts。 - サービスのモックパターン:
const module: TestingModule = await Test.createTestingModule({
providers: [
UserService,
{
provide: getRepositoryToken(User),
useValue: {
findOne: jest.fn(),
save: jest.fn(),
find: jest.fn(),
},
},
],
}).compile();
NestJS CLIコマンド
コード生成には以下のNestJS CLIパターンを使う:
nest g module {feature}— モジュールファイル生成とAppModule更新nest g controller {feature} --no-spec— specを別途管理する場合nest g service {feature}— specファイル付きでサービス生成- 必ず正しいフィーチャーディレクトリを指定する:
nest g service users/user
よくある間違い
- 循環依存: モジュールAがBをインポートしBがAをインポートする場合は
forwardRef(() => ModuleB)を使う——ただしアーキテクチャが正しいかを先に確認する。 - グローバルモジュール:
@Global()はPrismaServiceやConfigServiceのような真にアプリ全体で使うプロバイダーにのみ使う。 - 非同期モジュール初期化: 設定が別プロバイダーに依存する場合は
forRootAsync()パターンを使う。
## AGENTS.mdによるマルチエージェント設定
大規模なNestJSプロジェクト——複数のフィーチャードメイン、並行開発——では、Claude Codeのサブエージェントを特定モジュールに割り当てられる。AGENTS.mdファイルはディレクトリパスでエージェントの指示をスコープする。
```markdown
# AGENTS.md
## グローバルルール
このコードベース上のすべてのエージェントに適用:
- CLAUDE.mdのすべてのルールに従う
- タスクを完了とマークする前に`npm run lint`と`npm run test`を実行する
- `.env`ファイルを変更しない。環境変数は`ConfigService`経由でアクセスする
## /src/users/
エージェントスコープ: ユーザー管理ドメイン。
- このモジュールが`User`エンティティを管理する。他のモジュールはこれを直接変更しない。
- 認証は`/src/auth/`が担当——ここに認証ロジックを複製しない。
- `UserService`はエクスポートされ`AuthModule`から利用される。インターフェースを変更する際はauth側の変更と調整する。
## /src/auth/
エージェントスコープ: 認証と認可。
- JWT戦略はここに置く。ルートを保護するガードはこのモジュールからインポートする。
- プレーンテキストのパスワードを保存しない。常にコストファクター12のbcryptを使う。
- `AuthModule`はユーザー検索のために`UserModule`をインポートする——これは意図的であり循環依存ではない。
## /src/shared/
エージェントスコープ: 共有ユーティリティ、デコレーター、インターセプター、パイプ。
- ここのコードはフィーチャー固有のインポートをゼロにする。どのNestJSプロジェクトでも動くものだけ置く。
- フィーチャーモジュールからインポートしたくなった場合、そのコードはそのフィーチャーモジュールに置くべき。
## /test/
エージェントスコープ: エンドツーエンドテストのみ。
- `NestFactory.create(AppModule)`とsupertest でフルアプリを使う。
- テストデータのクリーンアップは`afterEach()`で処理する——常に状態を元に戻す。
Claude Codeが扱いやすいモジュール構造
CLAUDE.mdを書く前に、一貫したモジュール構造を確立しておくと良い。予測可能なレイアウトはアドホックな構成より、Claude Codeが大幅に正確にナビゲートできる。
フィーチャードメインごとに以下の構造を推奨する:
src/
users/
dto/
create-user.dto.ts
update-user.dto.ts
entities/
user.entity.ts
users.controller.ts
users.controller.spec.ts
users.service.ts
users.service.spec.ts
users.module.ts
auth/
guards/
jwt.guard.ts
roles.guard.ts
strategies/
jwt.strategy.ts
dto/
login.dto.ts
auth.controller.ts
auth.service.ts
auth.module.ts
shared/
interceptors/
logging.interceptor.ts
pipes/
parse-object-id.pipe.ts
decorators/
current-user.decorator.ts
このレイアウトがあると、CLAUDE.mdで具体的なパスを参照できる。「authコントローラーにレート制限を追加して」という指示に対して、Claude Codeはどこを見て何を変更すべきか正確に把握できる。
Claude Codeと相性の良いテストパターン
NestJSのテストにはAIエージェントが明示的なルールなしに詰まりやすい部分がある。以下の2パターンを設定することで信頼性が大きく向上する。
パターン1: 型付きモックファクトリー
Claude Codeにモック構造をソースから推測させるのではなく、各specファイルの冒頭に型付きのモックファクトリーを定義する:
// users.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { UserService } from './users.service';
import { User } from './entities/user.entity';
const mockUserRepository = () => ({
findOne: jest.fn(),
find: jest.fn(),
save: jest.fn(),
delete: jest.fn(),
create: jest.fn(),
});
type MockRepository<T = any> = Partial<Record<keyof Repository<T>, jest.Mock>>;
describe('UserService', () => {
let service: UserService;
let repository: MockRepository<User>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UserService,
{
provide: getRepositoryToken(User),
useFactory: mockUserRepository,
},
],
}).compile();
service = module.get<UserService>(UserService);
repository = module.get(getRepositoryToken(User));
});
このパターンを1つのspecファイルに確立しておくと、Claude Codeは新しいspecファイルでも正しく複製する。
パターン2: E2EテストのDBリセット
e2eテスト用に、ティアダウンで呼び出せるリセットユーティリティを定義する:
// test/helpers/reset-db.ts
import { DataSource } from 'typeorm';
export async function resetDatabase(dataSource: DataSource): Promise<void> {
const entities = dataSource.entityMetadatas;
for (const entity of entities) {
const repository = dataSource.getRepository(entity.name);
await repository.clear();
}
}
CLAUDE.mdに追加する:
## E2Eテストのティアダウン
DBに書き込むe2eテストの各テストケース後に`test/helpers/reset-db.ts`の`resetDatabase(dataSource)`を呼び出す。テストファイルで直接`TRUNCATE CASCADE`を使わない。
ガードとインターセプターのパターン
ガードとインターセプターは、明示的なルールなしにClaude Codeが最もミスをしやすい箇所だ。CLAUDE.mdの「よくある間違い」セクションに以下のパターンを追加することで、ほとんどの問題を防げる:
## ガード
- ガードは`CanActivate`を実装して`Observable<boolean> | Promise<boolean> | boolean`を返す必要がある。
- JWT検証は`@nestjs/passport`の`AuthGuard('jwt')`に任せる——ガード内でカスタムJWTデコードを書かない。
- ロールチェックは`@Roles()`メタデータデコレーターを読み込む別の`RolesGuard`を使う。
- ガードは`@UseGuards(JwtAuthGuard, RolesGuard)`で適用する——順序が重要。認証→ロールチェックの順。
## インターセプター
- ロギングとレスポンス変換用のインターセプターは`shared/interceptors/`に置く。
- サイドエフェクト(ロギング)には`tap()`、レスポンス変換には`map()`を使う。
- インターセプターで例外をthrowしない——それは例外フィルターの役割。
NestJS向けの効果的なプロンプトパターン
CLAUDE.mdの静的ルールに加えて、特定のプロンプトパターンがNestJS固有の作業でより良い結果を生む。
新しいフィーチャーモジュールを生成する場合:
“確立済みのパターンに従って
productsフィーチャーモジュールを作成してください。id、name、price(decimal)、stock(integer)、createdAtを持つProductエンティティが必要です。サービスはfindAll()、findOne(id)、create(dto)、update(id, dto)、remove(id)を公開してください。AppModuleに組み込んでください。”
既存のルートにガードを追加する場合:
“
GET /productsとGET /products/:id(これらはパブリックのまま)を除くProductsControllerのすべてのルートにJWT認証を追加してください。src/auth/guards/の既存のJwtAuthGuardを使ってください。ガード自体は変更しないでください。”
テストを書く場合:
“
ProductsService.create()のユニットテストを書いてください。このメソッドはrepository.create()とrepository.save()を呼び出し、同じnameの商品が既に存在する場合はConflictExceptionをthrowする必要があります。users.service.spec.tsのモックリポジトリパターンを使ってください。”
既存のパターンやファイルへの具体的な参照が重要だ。Claude Codeは抽象的な説明より具体的な参照をはるかにうまく扱える。
Prompt Shelfギャラリーとの連携
本番環境で使われているNestJSのCLAUDE.md実例をギャラリーで公開している。実際のコードベースで見られた有用なパターン:
- 各サービスがそのサービスのドメインにスコープされた専用CLAUDE.mdを持つマイクロサービス構成
- エージェントがイベントパブリッシャー担当かサブスクライバー担当かを明示したAGENTS.mdルールを持つイベント駆動アーキテクチャ
- リゾルバーとコントローラーのコードに別々のルールを持つREST+GraphQLハイブリッドプロジェクト
新しいNestJSプロジェクトを始めるなら、このテンプレートをベースにしてアーキテクチャが固まるにつれて調整していくのが効率的だ。最も重要なルールは、チームが既に下した実際の決定を反映したもの——DIパターン、ORM選択、テスト方針。それを先に書き出して、残りはClaude Codeにコンテキストから学ばせれば良い。