I'm migrating my NestJS backend from CommonJS to ES Modules (ESM) and encountering a critical issue where ConfigService is not being properly injected, causing the application to crash during startup.
Migration Context: This migration is required because I need to integrate the @openai/agents/realtime package, which has co-dependencies that exclusively use ESM and cannot be imported in a CommonJS environment.
Error Details
[Nest] ERROR [ExceptionHandler] Cannot read properties of undefined (reading 'get')TypeError: Cannot read properties of undefined (reading 'get') at DatabaseService constructorWhen I added debugging, I confirmed: Error: ConfigService dependency injection failed
Environment
- Node.js: 20.16.0
- NestJS: 10.4.15
- TypeScript: 5.5.4
- Package manager: npm 10.8.1
- Runtime: tsx (switched from ts-node due to ESM loader deprecation warnings)
package.json (relevant parts)
json
{ "type": "module", "scripts": { "start:dev": "nodemon --watch src --exec \"tsx src/main.ts\"" }}tsconfig.json
json
{ "compilerOptions": { "module": "ESNext", "moduleResolution": "Node", "esModuleInterop": true, "target": "ESNext", // ... other options }, "ts-node": { "esm": true }}app.module.ts
typescript
import { Module } from '@nestjs/common';import { ConfigModule } from '@nestjs/config';
import baseConfig from '@/config/base.config';import databaseConfig from '@/config/knex.config';// ... other config imports
@Module({ imports: [ ConfigModule.forRoot({ isGlobal: true, load: [baseConfig, databaseConfig, /* other configs */], ignoreEnvFile: true, }), DatabaseModule, // ... other modules ],})export class AppModule {}Failing Service
typescript
@Injectable()export class DatabaseService implements OnModuleInit { constructor(private readonly configService: ConfigService) { // configService is undefined here if (!this.configService) { throw new Error('ConfigService dependency injection failed'); } const databaseConfig = this.configService.get<DatabaseConfig>('database'); // ... }}What I've Tried
- Verified ConfigModule setup:
isGlobal: trueis set - Added .js extensions to local imports (ESM requirement)
- Switched from ts-node to tsx due to loader deprecation warnings
- Used
registerAs()in config files for proper namespace registration - Confirmed module import order: ConfigModule is imported before DatabaseModule
- Added extensive debugging: ConfigService is definitely
undefinedin constructor
Questions
- Are there known issues with NestJS dependency injection in ESM mode, especially when forced to migrate for ESM-only packages?
- Is there a specific order or timing issue with how modules are loaded in ESM that affects DI?
- Should I be using different decorators or patterns for ESM compatibility with external ESM-only dependencies?
- Are there additional configuration steps needed for NestJS + ESM beyond the standard migration guide when integrating ESM-only packages?
- Has anyone successfully integrated
@openai/agents/realtimeor similar ESM-only packages with NestJS?