nestjs/nest · critical · UndefinedDependencyException

Nest can't resolve dependencies of the ${type.toString()}

Error message

Nest can't resolve dependencies of the ${type.toString()}

What it means

Thrown while the injector resolves constructor parameters: the resolved dependency token is `undefined`. NestJS reads constructor types from the `design:paramtypes` metadata that TypeScript emits (requires `emitDecoratorMetadata`); when that metadata contains `undefined` the class cannot be instantiated, so the container fails fast with UndefinedDependencyException instead of passing `undefined` into your constructor. In nearly all cases the `undefined` comes from the ES module system, not from your module configuration.

Source

Thrown at packages/core/injector/injector.ts:519

  }

  public reflectSelfParams(type: Type<unknown> | Function): any[] {
    return Reflect.getMetadata(SELF_DECLARED_DEPS_METADATA, type) || [];
  }

  public async resolveSingleParam<T>(
    wrapper: InstanceWrapper<T>,
    param: Type<any> | string | symbol,
    dependencyContext: InjectorDependencyContext,
    moduleRef: Module,
    resolutionContext: ResolutionContext = { contextId: STATIC_CONTEXT },
    keyOrIndex?: symbol | string | number,
  ) {
    if (isUndefined(param)) {
      this.logger.log(
        'Nest encountered an undefined dependency. This may be due to a circular import or a missing dependency declaration.',
      );
      throw new UndefinedDependencyException(
        wrapper.name,
        dependencyContext,
        moduleRef,
      );
    }
    const token = this.resolveParamToken(wrapper, param);
    return this.resolveComponentWrapper(
      moduleRef,
      token,
      dependencyContext,
      wrapper,
      resolutionContext,
      keyOrIndex,
    );
  }

  public resolveParamToken<T>(
    wrapper: InstanceWrapper<T>,

View on GitHub (pinned to 39fbddae51)

Solutions

  1. Break the file-level circular import: move the shared types/interfaces to a third file that neither side depends on, or import one side directly instead of via a barrel.
  2. If the classes genuinely need each other, use `@Inject(forwardRef(() => OtherService)) private readonly other: OtherService` on the injection side (and `moduleRef`/`forwardRef` in modules if needed).
  3. Resolve the dependency lazily at runtime with `this.moduleRef.get(OtherService)` or `moduleRef.resolve()` inside a method instead of the constructor.
  4. Confirm `"emitDecoratorMetadata": true` and `"experimentalDecorators": true` in tsconfig.json, especially after switching to esbuild/swc based builds that drop metadata by default.
  5. Make sure the injected value is a concrete class or a real token, never an interface without `@Inject(token)`.

Example fix

// before (files import each other -> design:paramtypes captures undefined)
// a.service.ts
@Injectable()
export class AService {
  constructor(private readonly b: BService) {} // BService is undefined here
}

// after
import { Inject, forwardRef } from '@nestjs/common';

@Injectable()
export class AService {
  constructor(
    @Inject(forwardRef(() => BService))
    private readonly b: BService,
  ) {}
}
Defensive patterns

Strategy: validation

Validate before calling

// Pre-bootstrap scan: no provider constructor may reference undefined tokens
import 'reflect-metadata';

function assertNoUndefinedParams(classes: Function[]) {
  for (const cls of classes) {
    const params: any[] = Reflect.getOwnMetadata('design:paramtypes', cls) ?? [];
    params.forEach((t, i) => {
      if (t === undefined) {
        throw new Error(
          `${cls.name}: constructor param #${i} resolved to undefined — circular import or non-emitted type`,
        );
      }
    });
  }
}
// run before NestFactory.create(AppModule) with all provider classes

Type guard

const hasDefinedParamTypes = (cls: Function): cls is new (...args: any[]) => any =>
  (Reflect.getOwnMetadata('design:paramtypes', cls) ?? []).every(t => t !== undefined);

Try / catch

try {
  await NestFactory.create(AppModule);
} catch (e: any) {
  if (/can't resolve dependencies/i.test(String(e?.message))) {
    console.error('DI wiring failure at bootstrap:', e.message);
    process.exit(1);
  }
  throw e;
}

Prevention

When it happens

Trigger: resolveSingleParam is called with `param === undefined`: (1) two provider files import each other, so at decoration time the imported class binding is still `undefined`; (2) a provider is imported through a barrel file (index.ts) that participates in an import cycle; (3) `@Inject(TOKEN)` where the TOKEN constant is `undefined` when the decorator evaluates (again due to a cycle); (4) the parameter type is imported from a file whose exports are not yet initialized.

Common situations: Two services in different files reference each other's types; extracting classes into index.ts barrels during a refactor; introducing TypeScript path aliases that create cycles; importing a token constant from a file that also imports the consumer. Developers often see this right after adding a new cross-service dependency.

Related errors


AI-assisted analysis of nestjs/nest@39fbddae51 (2026-08-21). Data as JSON: /api/errors/8960482df8897617. Report an issue: GitHub.