NestJS Logo

Modules

A module is a class annotated with the @Module() decorator. The decorator provides metadata that Nest uses to organize and manage the application structure.

Every Nest application has at least one module, the root module. It is the starting point from which Nest builds the application graph, the internal structure Nest uses to resolve relationships and dependencies between modules and providers. A very small application may have only a root module, but most applications have multiple modules, each encapsulating a closely related set of capabilities. Modules are the recommended way to organize your components.

The @Module() decorator takes a single object with properties that describe the module:

providersthe providers that will be instantiated by the Nest injector and that may be shared at least across this module
controllersthe set of controllers defined in this module that must be instantiated
importsthe list of imported modules that export the providers required in this module
exportsthe subset of providers that this module provides and that should be available to other modules importing it. You can use either the provider itself or its token (the provide value)

A module encapsulates its providers by default: you can inject only providers that are part of the current module or that are explicitly exported by an imported module. A module's exported providers form its public interface, or API.

Feature modules#

In our example, CatsController and CatsService are closely related and serve the same application domain, so it makes sense to group them into a feature module. A feature module organizes the code for a specific feature, which keeps boundaries clear. This becomes more important as the application or team grows, and it aligns with the SOLID principles.

Next, we'll create a CatsModule that groups the controller and the service:

cats/cats.module.ts
JS TS

import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller.js';
import { CatsService } from './cats.service.js';

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {}
Hint To create a module with the CLI, run $ nest g module cats.

Above, we defined CatsModule in the cats.module.ts file and moved everything related to it into the cats directory. The last step is to import this module into the root module (AppModule, defined in the app.module.ts file):

app.module.ts
JS TS

import { Module } from '@nestjs/common';
import { CatsModule } from './cats/cats.module.js';

@Module({
  imports: [CatsModule],
})
export class AppModule {}

The directory structure now looks like this:

src
cats
dto
create-cat.dto.ts
interfaces
cat.interface.ts
cats.controller.ts
cats.module.ts
cats.service.ts
app.module.ts
main.ts

Shared modules#

In Nest, modules are singletons by default, so you can share the same instance of any provider between multiple modules.

Every module is automatically a shared module: once created, it can be reused by any other module. Suppose you want to share an instance of CatsService between several other modules. To do so, first export the CatsService provider by adding it to the module's exports array:

cats.module.ts
JS TS

import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller.js';
import { CatsService } from './cats.service.js';

@Module({
  controllers: [CatsController],
  providers: [CatsService],
  exports: [CatsService],
})
export class CatsModule {}

Any module that imports CatsModule now has access to CatsService and shares the same instance with every other module that imports it.

Registering CatsService directly in every module that needs it would also work, but each module would then get its own separate instance of the service. Multiple instances increase memory usage and can cause unexpected behavior, such as inconsistent state if the service holds internal state.

Encapsulating CatsService in a module such as CatsModule and exporting it ensures that every module importing CatsModule reuses the same instance. This reduces memory consumption and makes behavior more predictable, because shared state and resources are managed in one place. Sharing services efficiently across the application is one of the key benefits of modularity and dependency injection.

Module re-exporting#

As shown above, modules can export their internal providers. They can also re-export modules that they import. In the example below, CommonModule is both imported into and exported from CoreModule, which makes it available to any module that imports CoreModule.


@Module({
  imports: [CommonModule],
  exports: [CommonModule],
})
export class CoreModule {}

Dependency injection#

A module class can also inject providers (e.g., for configuration purposes):

cats.module.ts
JS TS

import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller.js';
import { CatsService } from './cats.service.js';

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {
  constructor(private catsService: CatsService) {}
}

import { Module, Dependencies } from '@nestjs/common';
import { CatsController } from './cats.controller.js';
import { CatsService } from './cats.service.js';

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
@Dependencies(CatsService)
export class CatsModule {
  constructor(catsService) {
    this.catsService = catsService;
  }
}

However, module classes themselves cannot be injected as providers due to circular dependencies.

Global modules#

Importing the same set of modules everywhere can become tedious. In Angular, providers are registered in the global scope and, once defined, are available everywhere. Nest, by contrast, encapsulates providers inside the module scope: you can't use a module's providers elsewhere without first importing the module that encapsulates them.

To make a set of providers available everywhere out of the box (e.g., helpers or database connections), make the module global with the @Global() decorator:


import { Module, Global } from '@nestjs/common';
import { CatsController } from './cats.controller.js';
import { CatsService } from './cats.service.js';

@Global()
@Module({
  controllers: [CatsController],
  providers: [CatsService],
  exports: [CatsService],
})
export class CatsModule {}

The @Global() decorator makes the module global-scoped. Register global modules only once, typically in the root or core module. In the example above, the CatsService provider is available everywhere, and modules that inject it don't need to add CatsModule to their imports array.

Hint Making everything global is not a recommended design practice. Global modules reduce boilerplate, but the imports array makes a module's API available to other modules in a controlled, explicit way. This keeps the application structure maintainable, shares only the parts of a module that others need, and avoids unnecessary coupling between unrelated parts of the application.

Dynamic modules#

Dynamic modules let you create modules that are configured at runtime. They are useful when a module's providers depend on options supplied by the module that imports it. For example, the following FeatureFlagsModule receives the application's feature flags through its static forRoot() method:

feature-flags.module.ts
JS TS

import { DynamicModule, Module } from '@nestjs/common';
import { FEATURE_FLAGS } from './feature-flags.constants.js';
import { FeatureFlagsService } from './feature-flags.service.js';

@Module({
  providers: [FeatureFlagsService],
  exports: [FeatureFlagsService],
})
export class FeatureFlagsModule {
  static forRoot(flags: Record<string, boolean>): DynamicModule {
    return {
      module: FeatureFlagsModule,
      providers: [{ provide: FEATURE_FLAGS, useValue: flags }],
    };
  }
}

import { Module } from '@nestjs/common';
import { FEATURE_FLAGS } from './feature-flags.constants.js';
import { FeatureFlagsService } from './feature-flags.service.js';

@Module({
  providers: [FeatureFlagsService],
  exports: [FeatureFlagsService],
})
export class FeatureFlagsModule {
  static forRoot(flags) {
    return {
      module: FeatureFlagsModule,
      providers: [{ provide: FEATURE_FLAGS, useValue: flags }],
    };
  }
}

The FEATURE_FLAGS injection token is a plain constant (export const FEATURE_FLAGS = 'FEATURE_FLAGS';), as described in Non-class-based provider tokens.

Hint The forRoot() method may return a dynamic module either synchronously or asynchronously (i.e., via a Promise).

The properties returned by forRoot()extend (rather than override) the metadata defined in the @Module() decorator. The resulting module therefore has both the statically declared FeatureFlagsService and the dynamically registered FEATURE_FLAGS provider. Because they belong to the same module, the service can inject the flags:

feature-flags.service.ts
JS TS

import { Inject, Injectable } from '@nestjs/common';
import { FEATURE_FLAGS } from './feature-flags.constants.js';

@Injectable()
export class FeatureFlagsService {
  constructor(
    @Inject(FEATURE_FLAGS) private readonly flags: Record<string, boolean>,
  ) {}

  isEnabled(flag: string): boolean {
    return this.flags[flag] ?? false;
  }
}

import { Dependencies, Injectable } from '@nestjs/common';
import { FEATURE_FLAGS } from './feature-flags.constants.js';

@Injectable()
@Dependencies(FEATURE_FLAGS)
export class FeatureFlagsService {
  constructor(flags) {
    this.flags = flags;
  }

  isEnabled(flag) {
    return this.flags[flag] ?? false;
  }
}

The module exports only FeatureFlagsService, so the flags themselves stay an implementation detail of the module.

Import and configure the FeatureFlagsModule as follows:

app.module.ts
JS TS

import { Module } from '@nestjs/common';
import { FeatureFlagsModule } from './feature-flags/feature-flags.module.js';

@Module({
  imports: [
    FeatureFlagsModule.forRoot({
      newCheckout: true,
      betaDashboard: false,
    }),
  ],
})
export class AppModule {}

Only the module that imports FeatureFlagsModule.forRoot() can inject FeatureFlagsService. Calling forRoot() again in another module would create a second, separately configured instance. To share a single instance, configure the module once and re-export it. To re-export a dynamic module, list the module class in the exports array, without calling forRoot() again:

core.module.ts
JS TS

import { Module } from '@nestjs/common';
import { FeatureFlagsModule } from '../feature-flags/feature-flags.module.js';

@Module({
  imports: [
    FeatureFlagsModule.forRoot({
      newCheckout: true,
      betaDashboard: false,
    }),
  ],
  exports: [FeatureFlagsModule],
})
export class CoreModule {}

Every module that imports CoreModule can now inject FeatureFlagsService, and all of them share the same flags.

Alternatively, to register a dynamic module in the global scope, set the global property to true in the object returned by forRoot(). FeatureFlagsService then becomes injectable everywhere, without importing any module:


return {
  global: true,
  module: FeatureFlagsModule,
  providers: [{ provide: FEATURE_FLAGS, useValue: flags }],
};
Warning As mentioned above, making everything global is not a good design decision.

Options known only at runtime, such as values read by ConfigService, call for an asynchronous variant of forRoot(). The Dynamic modules chapter covers this and more.

Hint To learn how to build highly customizable dynamic modules with ConfigurableModuleBuilder, see the Configurable module builder section.
Edit on GitHub

Support us

Nest is an MIT-licensed open source project. It can grow thanks to the support of these awesome people. If you'd like to join them, please read more here.

Principal Sponsors

SerpApi LogoTrilon LogoMojam Logo

Sponsors / Partners

Become a sponsor