nestjs/nest · error · InvalidClassException

ModuleRef cannot instantiate class (${value} is not construc

Error message

ModuleRef cannot instantiate class (${value} is not constructable).

What it means

`ModuleRef.create(type)` instantiates a class that does NOT need to be registered in the DI container, but the argument must be a real constructor: a function with a prototype. InvalidClassException ('ModuleRef cannot instantiate class (X is not constructable)') means the value passed is undefined, a string token, an arrow function without prototype data, or an interface that compiled away to nothing.

Source

Thrown at packages/core/injector/module.ts:651

        options: ModuleRefGetOrResolveOpts = {},
      ): Promise<TResult | Array<TResult>> {
        options.strict ??= true;
        options.each ??= false;

        return this.resolvePerContext<TInput, TResult>(
          typeOrToken,
          self,
          contextId,
          options,
        );
      }

      public async create<T = any>(
        type: Type<T>,
        contextId?: ContextId,
      ): Promise<T> {
        if (!(type && isFunction(type) && type.prototype)) {
          throw new InvalidClassException(type);
        }
        return this.instantiateClass<T>(type, self, contextId);
      }
    };
  }

  private isEntryProvider(metatype: InjectionToken): boolean {
    return typeof metatype === 'function'
      ? !!Reflect.getMetadata(ENTRY_PROVIDER_WATERMARK, metatype)
      : false;
  }

  private generateUuid(): string {
    const prefix = 'M_';
    const key = this.token
      ? this.token.includes(':')
        ? this.token.split(':')[1]
        : this.token

View on GitHub (pinned to a3a31b9643)

Solutions

  1. Pass the concrete imported class reference itself: `moduleRef.create(MyJobHandler)`.
  2. Ensure the import is a value import, not `import type` / interface-only, and that it is not undefined due to a circular import.
  3. If you only hold a string/symbol token, resolve a registered provider with `moduleRef.get(token)` instead of `create`.
  4. For arrow-function factories, register them as `{ provide, useFactory }` and let the container call them.

Example fix

// before
const handler = await this.moduleRef.create('email-job' as any);

// after
import { EmailJobHandler } from './jobs/email-job.handler';
const handler = await this.moduleRef.create(EmailJobHandler);
Defensive patterns

Strategy: type-guard

Validate before calling

const isConstructable = (v: any): v is new (...args: any[]) => any =>
  typeof v === 'function' && typeof v.prototype === 'object';

if (!isConstructable(targetType)) {
  throw new Error(`Refusing moduleRef.create on non-class ${String(targetType)}`);
}
const instance = await this.moduleRef.create(targetType);

Type guard

declare const TypeBrand: unique symbol;
function isConcreteClass<T>(v: unknown): v is abstract new (...args: any[]) => T {
  return typeof v === 'function' && !!(v as any).prototype;
}

Prevention

When it happens

Trigger: Calling `moduleRef.create(SomeInterface)` where the 'type' is only a TypeScript type; passing a variable that resolved to `undefined` (circular import or missing import); passing a string/symbol token used with `@Inject`; passing an already-created instance or a plain object; passing a lazy `() => Class` instead of the class itself.

Common situations: Dynamically instantiating strategies/handlers by class reference stored in config maps; code generators emitting type-only imports (`import type`); refactors where the class import got dropped but the reference remained typed as any.

Related errors


AI-assisted analysis of nestjs/nest@a3a31b9643 (2026-08-21). Data as JSON: /api/errors/6befcdd633daa98a. Report an issue: GitHub.