Modules
Understand how NestJS modules organize an application into features, how imports and exports share providers, and when to use global and dynamic modules.
On this page 12 sections
Introduction
Every NestJS application is a tree of modules. The root module (AppModule) sits at the top, and each feature of your app — users, products, orders, authentication — lives in its own module underneath. Understanding modules is the key to understanding how Nest finds and connects everything else.
- What a module is and why Nest uses them.
- The four properties of the @Module() decorator.
- How to create and register a feature module.
- How to share a provider between modules with exports.
- What global and dynamic modules are, and when to use them.
What is a Module?
A module is a class annotated with @Module() that groups a closely related set of capabilities. Think of it like a department in a company: the "Users" department has its own staff (providers) and its own front desk (controllers), and it can choose which staff it lends to other departments.
Nest uses the module tree to build its dependency injection container. When a controller asks for a service, Nest only looks inside that controller's own module and the modules it imports — this boundary is what keeps large applications organized.
The @Module() Decorator
| Property | Purpose |
|---|---|
| providers | Services and other providers created by this module and available inside it. |
| controllers | Controllers defined in this module, which Nest instantiates and routes to. |
| imports | Other modules whose exported providers this module needs. |
| exports | The subset of this module's providers that importing modules are allowed to use. |
Providers are private to their module unless they are listed in exports. This is deliberate — it stops modules from reaching into each other's internals.
Creating a Feature Module
nest g module usersnest g controller usersnest g service users// src/users/users.module.tsimport { Module } from '@nestjs/common';import { UsersController } from './users.controller.js';import { UsersService } from './users.service.js';
@Module({ controllers: [UsersController], providers: [UsersService],})export class UsersModule {}The CLI also adds UsersModule to the imports array of AppModule automatically. That one import is what connects the whole users feature to your application:
// src/app.module.tsimport { Module } from '@nestjs/common';import { UsersModule } from './users/users.module.js';
@Module({ imports: [UsersModule],})export class AppModule {}Sharing Providers with Exports
Suppose an OrdersService needs to look up users. Instead of creating a second UsersService, UsersModule exports its service, and OrdersModule imports UsersModule:
// src/users/users.module.ts@Module({ controllers: [UsersController], providers: [UsersService], exports: [UsersService], // now other modules may use it})export class UsersModule {}
// src/orders/orders.module.ts@Module({ imports: [UsersModule], // brings in UsersService controllers: [OrdersController], providers: [OrdersService],})export class OrdersModule {}// src/orders/orders.service.ts@Injectable()export class OrdersService { constructor(private readonly usersService: UsersService) {}}Providers are singletons by default, so OrdersModule receives the exact same UsersService instance that UsersModule uses — modules share one instance rather than each creating their own.
Global Modules
Some providers — configuration, logging, a database connection — are needed almost everywhere. Importing their module into every feature module gets repetitive, so Nest lets you mark a module as global with @Global():
import { Global, Module } from '@nestjs/common';import { LoggerService } from './logger.service.js';
@Global()@Module({ providers: [LoggerService], exports: [LoggerService],})export class LoggerModule {}Register a global module once (usually in AppModule) and its exported providers become injectable everywhere without further imports.
Making everything global defeats the purpose of modules — it hides which features depend on what. Reserve it for genuinely cross-cutting infrastructure.
Dynamic Modules
A dynamic module is configured at import time by calling a static method, conventionally named forRoot() (configure once for the whole app) or forFeature() (configure per feature module). You will use many of these from official packages:
@Module({ imports: [ ConfigModule.forRoot({ isGlobal: true }), // lesson 11 TypeOrmModule.forRoot({ /* db options */ }), // lesson 12 UsersModule, ],})export class AppModule {}You rarely need to write your own dynamic module as a beginner, but recognizing the forRoot()/forFeature() pattern makes the official packages much easier to understand.
Common Mistakes
- Adding the same service to the providers array of two modules — this creates two separate instances instead of sharing one. Export it and import the module instead.
- Importing a module but forgetting that the provider you need was never exported.
- Listing a provider in exports without also listing it in providers (or importing it).
- Putting every feature directly inside AppModule instead of creating feature modules.
Best Practices
- Create one module per business feature (users, products, orders, auth).
- Export only what other modules genuinely need — keep the rest private.
- Keep AppModule thin: it should mostly import feature and infrastructure modules.
- Use @Global() only for cross-cutting infrastructure like configuration or logging.
Frequently Asked Questions
It almost always means the provider X needs is not available in X's module — either it is not in providers, or its module is not imported, or it is not exported from the module that owns it.
Circular imports are possible using forwardRef(), but they usually signal a design problem. Prefer extracting the shared logic into a third module both can import.
Yes — a module can list an imported module in its exports, so anything importing it also gets that module's exported providers.
Key Takeaways
- Modules are classes decorated with @Module() that group related functionality.
- @Module() has four properties: providers, controllers, imports and exports.
- Providers are private to their module unless exported.
- Exported providers are shared as a single instance across importing modules.
- @Global() modules and dynamic modules (forRoot/forFeature) handle app-wide infrastructure.
Summary
You learned how modules organize a Nest app into features, how imports and exports share providers safely, and when global and dynamic modules are useful. Next, you will build the entry points of your API — controllers and routing.