Global Modules & ModuleRef

Register cross-cutting providers once with @Global, and resolve dependencies imperatively with ModuleRef.

Some providers - a logger, a config service, an event bus - are needed almost everywhere. Importing their module into twenty feature modules is noise. A global module registers its exports once and makes them injectable app-wide.

Marking a module global

Add the @Global() decorator (or global: true on a dynamic module). You still import it once, typically in AppModule; after that its exported providers are available anywhere without re-importing.

@Global()
@Module({
  providers: [LoggerService],
  exports: [LoggerService],
})
export class LoggerModule {}

Use it sparingly

Global modules trade explicitness for convenience. Overuse and your dependency graph becomes invisible - a reader can no longer tell what a module needs by looking at its imports. Reserve @Global() for genuinely universal infrastructure, not for ordinary feature services.

ModuleRef: DI without the constructor

Sometimes you must resolve a provider dynamically - the token isn't known until runtime, or you need a fresh REQUEST-scoped instance. ModuleRef is the container's runtime handle:

  • moduleRef.get(Token) - fetch an existing singleton.
  • moduleRef.resolve(Token) - create a scoped/transient instance (returns a Promise).
  • moduleRef.create(SomeClass) - instantiate a class that isn't a registered provider, with its deps injected.

This is how plugin systems and strategy resolvers are built in Nest: a registry maps a name to a token, and ModuleRef materializes the right provider on demand.

Example

Example Β· typescript
import {
  Global,
  Module,
  Injectable,
  OnModuleInit,
} from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';

@Injectable()
export class LoggerService {
  log(msg: string) {
    console.log(`[log] ${msg}`);
  }
}

@Global()
@Module({
  providers: [LoggerService],
  exports: [LoggerService],
})
export class LoggerModule {}
// Imported once in AppModule β€” now injectable everywhere,
// no repeated imports in feature modules.

// A strategy registry resolved at runtime via ModuleRef.
interface Notifier {
  send(msg: string): string;
}

@Injectable()
export class EmailNotifier implements Notifier {
  send(msg: string) {
    return `email:${msg}`;
  }
}

@Injectable()
export class SmsNotifier implements Notifier {
  send(msg: string) {
    return `sms:${msg}`;
  }
}

@Injectable()
export class NotificationDispatcher implements OnModuleInit {
  private registry = new Map<string, any>();

  constructor(private readonly moduleRef: ModuleRef) {}

  onModuleInit() {
    this.registry.set('email', EmailNotifier);
    this.registry.set('sms', SmsNotifier);
  }

  dispatch(channel: string, msg: string) {
    const token = this.registry.get(channel);
    // strict:false lets us reach providers in other modules
    const notifier = this.moduleRef.get<Notifier>(token, { strict: false });
    return notifier.send(msg);
  }
}

When to use it

  • A developer marks a CoreModule as @Global so Logger, Config, and Metrics providers are injectable everywhere without each feature module importing CoreModule.
  • An engineer uses ModuleRef.get() to resolve a provider imperatively inside a factory function or lifecycle hook where constructor injection is not available.
  • A team uses ModuleRef.resolve() for a request-scoped service inside a background job that runs outside the normal request lifecycle.

More examples

@Global module declaration

Marks CoreModule as @Global so its LoggerService and MetricsService are available for injection in any module without importing CoreModule each time.

Example Β· ts
import { Global, Module } from '@nestjs/common';
import { LoggerService } from './logger.service';
import { MetricsService } from './metrics.service';

@Global()
@Module({
  providers: [LoggerService, MetricsService],
  exports: [LoggerService, MetricsService],
})
export class CoreModule {}

Imperative resolve with ModuleRef

Uses ModuleRef.get() to resolve PaymentsService imperatively during onModuleInit, useful for breaking constructor-level circular references.

Example Β· ts
import { Injectable, OnModuleInit } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import { PaymentsService } from '../payments/payments.service';

@Injectable()
export class OrdersService implements OnModuleInit {
  private paymentsService: PaymentsService;

  constructor(private moduleRef: ModuleRef) {}

  onModuleInit() {
    this.paymentsService = this.moduleRef.get(PaymentsService, { strict: false });
  }
}

ModuleRef.resolve for scoped providers

Uses ModuleRef.resolve with a manually created ContextId to instantiate a request-scoped service from within a background job.

Example Β· ts
import { Injectable } from '@nestjs/common';
import { ModuleRef, ContextIdFactory } from '@nestjs/core';
import { RequestContextService } from './request-context.service';

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

  async run() {
    const contextId = ContextIdFactory.create();
    const ctx = await this.moduleRef.resolve(RequestContextService, contextId);
    // ctx is a fresh REQUEST-scoped instance tied to this contextId
  }
}

Discussion

  • Be the first to comment on this lesson.