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
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.
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.
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.
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