4.4 KiB
4.4 KiB
Circular Dependency Verification Pattern
Problem
ESM circular dependencies in TypeScript projects (especially NestJS with TypeORM) fail at runtime, not during:
tsc --noEmit(typecheck)nest build(compilation)- Even successful builds can crash on startup
Traditional approaches are too slow or unsafe:
- ❌ Starting the full app (
./run up) takes 3+ minutes - ❌ Running
node dist/main.jsstarts servers, connects to DBs (side effects)
Solution
Fast, safe verification that catches circular deps in ~5 seconds:
- Build the project
- Import
AppModule(triggers decorators without bootstrapping) - No servers started, no DB connections, no side effects
Implementation
1. Create verification script
Create scripts/verify-circular-deps.mjs in your NestJS project:
#!/usr/bin/env node
/**
* Verify Circular Dependencies
*
* Safely checks for circular dependency issues by importing the AppModule
* without bootstrapping the application (no server start, no DB connections).
*
* Usage: node scripts/verify-circular-deps.mjs
*/
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const distPath = join(__dirname, '..', 'dist');
// Prevent application from actually starting
process.env.NODE_ENV = 'test';
process.env.SKIP_BOOTSTRAP = 'true';
console.log('🔍 Checking for circular dependencies...\n');
try {
// Import AppModule - this triggers all decorators without starting the app
const appModulePath = join(distPath, 'app.module.js');
await import(appModulePath);
console.log('✅ No circular dependency issues detected');
console.log(' All modules and entities loaded successfully\n');
process.exit(0);
} catch (error) {
console.error('❌ Circular dependency detected!\n');
console.error('Error:', error.message);
console.error('\nStack trace:');
console.error(error.stack);
console.error('\n💡 Hint: Look for entities with bidirectional relations.');
console.error(' Use string references in decorators: @ManyToOne(\'EntityName\', ...)\n');
process.exit(1);
}
2. Add to package.json
{
"scripts": {
"verify": "pnpm build && node scripts/verify-circular-deps.mjs"
}
}
3. Usage
# During development
pnpm verify
# In CI/CD (add to your pipeline)
pnpm verify || exit 1
Common Causes & Fixes
TypeORM Bidirectional Relations
Problem:
// user.entity.ts
import { Post } from './post.entity'
@OneToMany(() => Post, post => post.author)
posts!: Post[]
// post.entity.ts
import { User } from './user.entity'
@ManyToOne(() => User, user => user.posts)
author!: User
Solution: Use string references
// user.entity.ts
@OneToMany('Post', post => post.author)
posts!: any[]
// post.entity.ts
@ManyToOne('User', user => user.posts)
author!: any
NestJS Module Circular Imports
Problem:
// users.module.ts
import { PostsModule } from './posts.module'
// posts.module.ts
import { UsersModule } from './users.module'
Solution: Use forwardRef()
// users.module.ts
@Module({
imports: [forwardRef(() => PostsModule)]
})
// posts.module.ts
@Module({
imports: [forwardRef(() => UsersModule)]
})
Performance
| Method | Time | Safe | Catches Issue |
|---|---|---|---|
tsc --noEmit |
~2s | ✅ | ❌ (type-level only) |
pnpm build |
~3s | ✅ | ❌ (just transpiles) |
pnpm verify |
~5s | ✅ | ✅ (runtime check) |
./run up |
~3m | ❌ | ✅ (but too slow) |
node dist/main.js |
~10s | ❌ | ✅ (side effects!) |
Integration
Local Development
Add to pre-commit hook or run before pushing:
pnpm verify && git push
CI/CD (Forgejo Actions)
- name: Verify no circular dependencies
run: pnpm verify
Pre-deployment Check
#!/bin/bash
# deploy.sh
pnpm verify || {
echo "❌ Circular dependency detected. Fix before deploying."
exit 1
}
pnpm build
# ... continue deployment
Rollout Plan
Apply this pattern to all NestJS backend services:
landing/backend-apimarketplace/backend-apisso/apiwebmap/router- Other NestJS services...
References
- Original issue:
landing-apicircular deps between entities - Detection time: 3 minutes (via
./run up) → 5 seconds (viapnpm verify) - Date added: 2026-01-21