Modules are the organizational unit of every NestJS application. This lesson explains what the @Module() decorator does, how feature modules are structured, and how modules share providers with each other.
What Is a NestJS Module?
A module is a class annotated with @Module() that groups a cohesive set of controllers and providers, typically everything related to one feature or domain, like UsersModule or OrdersModule. Every Nest application has at least one module, the root AppModule.
The @Module() decorator accepts a metadata object with imports, controllers, providers, and exports. Nest uses this metadata to build the dependency injection graph before the application starts.
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
@Module({
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
exports: [UsersService] makes UsersService available to any other module that adds UsersModule to its own imports array.
The @Module() Metadata Object
@Module({
imports: [], // other modules this module depends on
controllers: [], // controllers registered by this module
providers: [], // injectable classes owned by this module
exports: [], // providers shared with importing modules
})
export class FeatureModule {}
imports pulls in providers exported by other modules.
controllers registers route handlers, they are not injectable and cannot be exported.
providers lists services, repositories, or any @Injectable() class owned by this module.
exports decides which of this module's own providers other modules may use.
Module Metadata Cheatsheet
What each property of @Module() controls.
Property
Accepts
Effect
imports
Array of modules
Grants access to their exported providers
controllers
Array of controller classes
Registers routes with the HTTP layer
providers
Array of injectable classes
Registers services in this module's scope
exports
Array of providers
Shares providers with importing modules
Feature Modules
Feature modules group everything related to a specific domain, keeping AppModule small and readable. A typical mid-size app has one module per resource (UsersModule, OrdersModule, AuthModule) imported into the root module.
@Module({
imports: [UsersModule, OrdersModule, AuthModule],
})
export class AppModule {}
Shared Modules
In NestJS, every module is a singleton by default, importing the same module in two places reuses the same provider instances rather than creating duplicates. This makes it safe to import a common module (like a DatabaseModule) from many feature modules without worrying about multiple connections being created.
A module is only ever instantiated once across the entire application.
Re-importing a module in multiple places shares, not duplicates, its exported providers.
Use a dedicated shared module for cross-cutting providers like logging or configuration.
Common Mistakes
Forgetting to add a provider to exports, then wondering why importing modules can't inject it.
Listing a controller inside providers instead of controllers (or vice versa for a service).
Creating one giant module for the whole app instead of splitting into feature modules.
Not realizing modules are singletons, and being surprised that state is shared across imports.
Key Takeaways
Modules group related controllers and providers behind the @Module() decorator.
imports grants access to another module's exported providers.
Only providers listed in exports are usable by importing modules.
Modules are singletons, importing one twice does not create duplicate instances.
Pro Tip
Give every feature module a single, obvious responsibility (one resource or bounded context per module). If a module's providers array starts feeling like a grab-bag of unrelated classes, that's usually a sign it should be split.
You now understand how NestJS modules organize an application. Next, learn about NestJS Controllers, where incoming requests are actually handled.