Referencias a módulos ModuleRef

Intermedio
Nest
Nest
Actualizado: 05/05/2026

Diagrama: tutorial-nest-moduleref

Referencias con ModuleRef

La clase ModuleRef proporciona una forma de acceder dinámicamente a los proveedores registrados en el contenedor de inyección de dependencias de NestJS. Esta funcionalidad resulta especialmente útil cuando necesitas obtener referencias a servicios de manera programática, sin conocer de antemano qué proveedor específico necesitarás.

flowchart LR
    Module[AppModule DI Container] --> Singleton[Providers SINGLETON]
    Module --> Request[Providers REQUEST]
    Module --> Transient[Providers TRANSIENT]
    Service[Servicio consumidor] -->|inyecta| ModuleRef[ModuleRef]
    ModuleRef -->|get token| Resolve{Resolución}
    Resolve -->|strict: false| Optional[Devuelve null si falta]
    Resolve -->|strict por defecto| Throw[Lanza UnknownDependency]
    Resolve --> Lookup[Lookup en DI Container]
    Lookup --> Singleton
    ModuleRef -->|resolve token| Scoped["Crea instancia REQUEST/TRANSIENT por contexto"]
    Scoped --> Request
    Scoped --> Transient

El diagrama muestra los dos métodos principales: get(token) busca instancias SINGLETON ya construidas y resolve(token) crea una instancia para providers REQUEST o TRANSIENT con contextId propio. La diferencia es relevante cuando el provider depende del request actual.

ModuleRef actúa como un registro interno que mantiene referencias a todos los proveedores disponibles en el contexto actual del módulo. A diferencia de la inyección de dependencias tradicional, donde declaras explícitamente las dependencias en el constructor, ModuleRef te permite resolver dependencias en tiempo de ejecución.

Inyección básica de ModuleRef

Para utilizar ModuleRef en tu servicio, simplemente inyéctalo a través del constructor como cualquier otra dependencia:

import { Injectable } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';

@Injectable()
export class DynamicService {
  constructor(private moduleRef: ModuleRef) {}

  async processData() {
    // Aquí podremos usar moduleRef para obtener proveedores
  }
}

El ModuleRef se inyecta automáticamente desde el paquete @nestjs/core y no requiere configuración adicional en el módulo.

Obtención de proveedores con get()

El método get() es la forma principal de obtener referencias a proveedores registrados. Acepta el token del proveedor (generalmente la clase del servicio) y devuelve la instancia correspondiente:

@Injectable()
export class UserProcessor {
  constructor(private moduleRef: ModuleRef) {}

  async processUser(userId: string) {
    // Obtener el servicio de usuarios dinámicamente
    const userService = this.moduleRef.get(UserService);
    
    // Usar el servicio obtenido
    const user = await userService.findById(userId);
    return user;
  }
}

También puedes usar tokens personalizados si has registrado proveedores con identificadores específicos:

// En el módulo
@Module({
  providers: [
    {
      provide: 'DATABASE_CONNECTION',
      useValue: createDatabaseConnection(),
    },
  ],
})
export class AppModule {}

// En el servicio
@Injectable()
export class DataService {
  constructor(private moduleRef: ModuleRef) {}

  getConnection() {
    // Obtener proveedor por token personalizado
    const connection = this.moduleRef.get('DATABASE_CONNECTION');
    return connection;
  }
}

Resolución estricta y opcional

Por defecto, el método get() utiliza resolución estricta, lo que significa que lanzará una excepción si el proveedor no existe. Puedes modificar este comportamiento usando el segundo parámetro:

@Injectable()
export class FlexibleService {
  constructor(private moduleRef: ModuleRef) {}

  async tryGetService() {
    // Resolución estricta (comportamiento por defecto)
    try {
      const service = this.moduleRef.get(OptionalService);
      return service;
    } catch (error) {
      console.log('Servicio no encontrado');
    }

    // Resolución no estricta
    const service = this.moduleRef.get(OptionalService, { strict: false });
    
    if (service) {
      return service.process();
    }
    
    return 'Servicio no disponible';
  }
}

Casos de uso prácticos

ModuleRef resulta especialmente útil en escenarios donde necesitas flexibilidad dinámica. Un caso común es la implementación de factorías que crean diferentes tipos de procesadores según el contexto:

@Injectable()
export class ProcessorFactory {
  constructor(private moduleRef: ModuleRef) {}

  createProcessor(type: string) {
    switch (type) {
      case 'email':
        return this.moduleRef.get(EmailProcessor);
      case 'sms':
        return this.moduleRef.get(SmsProcessor);
      case 'push':
        return this.moduleRef.get(PushProcessor);
      default:
        throw new Error(`Processor type ${type} not supported`);
    }
  }

  async processNotification(type: string, data: any) {
    const processor = this.createProcessor(type);
    return await processor.process(data);
  }
}

Otro escenario útil es la resolución condicional de servicios basada en configuración o estado de la aplicación:

@Injectable()
export class PaymentService {
  constructor(
    private moduleRef: ModuleRef,
    private configService: ConfigService,
  ) {}

  async processPayment(amount: number) {
    const environment = this.configService.get('NODE_ENV');
    
    // Usar diferentes procesadores según el entorno
    const processorToken = environment === 'production' 
      ? 'STRIPE_PROCESSOR' 
      : 'MOCK_PROCESSOR';
    
    const processor = this.moduleRef.get(processorToken);
    return await processor.charge(amount);
  }
}

Limitaciones y consideraciones

Es importante entender que ModuleRef solo puede acceder a proveedores que estén disponibles en el contexto actual del módulo. Esto incluye proveedores definidos en el mismo módulo y aquellos importados desde otros módulos.

Si intentas acceder a un proveedor que no está disponible en el contexto actual, recibirás un error de resolución. En estos casos, asegúrate de que el módulo correspondiente esté correctamente importado:

@Module({
  imports: [UserModule], // Necesario para acceder a UserService
  providers: [ProcessorFactory],
})
export class ProcessorModule {}

La resolución dinámica también tiene implicaciones en el rendimiento, ya que se realiza en tiempo de ejecución en lugar de tiempo de compilación. Úsala cuando realmente necesites flexibilidad dinámica, no como reemplazo de la inyección de dependencias tradicional.

resolve() para providers REQUEST y TRANSIENT

get() solo funciona con providers SINGLETON. Para REQUEST o TRANSIENT debes usar resolve() con un contextId que NestJS asocia al request actual:

import { ContextIdFactory, ModuleRef } from '@nestjs/core';

@Injectable()
export class TenantAwareService {
  constructor(private readonly moduleRef: ModuleRef) {}

  async run(request: Request) {
    const contextId = ContextIdFactory.getByRequest(request);
    const repository = await this.moduleRef.resolve(
      TenantRepository,
      contextId,
      { strict: false },
    );
    return repository.findAll();
  }
}

Sin contextId cada resolve() crearía una instancia nueva del repositorio, lo que multiplicaría conexiones a base de datos en cada llamada. Con getByRequest la instancia se reutiliza durante el ciclo del request.

Patrón Strategy con ModuleRef

ModuleRef brilla en arquitecturas plugin donde el conjunto de estrategias se descubre por configuración o etiquetas. Combinándolo con Reflect.getMetadata se puede registrar servicios con metadatos y resolverlos por nombre sin acoplar el orquestador a cada implementación:

export const STRATEGY_KEY = 'strategy:type';
export const Strategy = (type: string) => SetMetadata(STRATEGY_KEY, type);

@Injectable()
@Strategy('csv')
export class CsvExporter implements Exporter { /* ... */ }

@Injectable()
@Strategy('xlsx')
export class XlsxExporter implements Exporter { /* ... */ }

@Injectable()
export class ExportService {
  constructor(
    private readonly moduleRef: ModuleRef,
    private readonly discoveryService: DiscoveryService,
  ) {}

  async export(format: string, data: unknown[]) {
    const provider = this.discoveryService
      .getProviders()
      .find((p) => Reflect.getMetadata(STRATEGY_KEY, p.metatype ?? {}) === format);
    if (!provider) throw new BadRequestException(`Formato ${format} no soportado`);
    const exporter = this.moduleRef.get<Exporter>(provider.metatype, { strict: false });
    return exporter.run(data);
  }
}

Antipatrones a evitar

  • Service Locator generalizado: usar ModuleRef para todo en lugar de inyección por constructor empeora la testabilidad y oculta el grafo real de dependencias.
  • get() con REQUEST providers: lanza error en tiempo de ejecución; usa siempre resolve() para scopes no singleton.
  • Resolución en bucle caliente: cachear el resultado en una propiedad del servicio o en un Map para evitar coste por llamada.
  • Dependencias entre módulos no importados: si un módulo A intenta resolver providers de B sin importarlo, falla. Usa imports: [BModule] o forwardRef cuando haya ciclos.

ModuleRef es una herramienta para casos específicos: factorías dinámicas, plugins descubiertos en runtime, integración con librerías externas que entregan instancias por nombre. Para todo lo demás, la inyección por constructor sigue siendo la opción correcta.

Fuentes y referencias

Documentación oficial y recursos externos para profundizar en Nest

Documentación oficial de Nest
Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Nest es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Nest

Explora más contenido relacionado con Nest y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

Comprender qué es ModuleRef y su función en NestJS. Aprender a inyectar y utilizar ModuleRef para obtener proveedores dinámicamente. Conocer el método get() para resolver proveedores por token o clase. Entender la resolución estricta y opcional de proveedores con ModuleRef. Identificar casos prácticos y limitaciones del uso de ModuleRef en aplicaciones NestJS.