What is Modules in NestJS

hasabTech is a developer-focused educational platform committed to simplifying tech learning. We share practical coding tutorials, programming guides, and tech insights to help beginners and aspiring developers build real world skills. Our mission is to make technical education accessible, clear, and community driven. We are currently building our in house products, experimenting with real world tech solutions, and sharing what we learn along the way.
Understanding Modules in NestJS
In this blog, we will discuss an essential concept in NestJS; Modules.
So far, we have been using the main module to register controllers and providers but we have not specifically explored what a module actually is in NestJS.
What is a Module?
A module in NestJS is a class annotated with the @Module() decorator. This decorator accepts an object with four properties:
imports for importing other modules.
exports for exporting providers to other modules.
controllers for declaring route handlers.
providers for declaring the services or providers used in the module.
providers for declaring the services or providers used in the module.
You have likely seen the last two (controllers and providers) used frequently, but the full structure gives us more flexibility and modularity in our application.
When should you create a Module?
You should create a new module when you want to encapsulate a set of closely related features. For example:
A UserModule might contain a UserController and a UserService.
An AlbumModule might contain an AlbumController and an AlbumService.
This structure helps you organise your codebase in a modular and scalable way.
nest generate module users
This command automatically creates a new module file, which you can then connect with the relevant controller and service
Registering Modules
Once you have created multiple feature modules (e.g., UserModule, AlbumModule), you must register them in the imports array of the root module:
@Module({
@Module({
imports : [CatsModule, RequestModule, ResponseModule, RedirectModule]})
export class AppModule {}
Creating a Module in NestJS: Example with CatsModule
In our cats folder, we have a file named cats.module.ts. Let’s walk through what we have done here and why it matters.
Step-by-Step Breakdown
First, we import the necessary items from NestJS:
import { Module } from '@nestjs/common';
import { CatsService } from './cats.service';
import { CatsController } from './cats.controller';
Creating and Exporting the Module
Next, we define and export a class named CatsModule
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {}
In NestJS, modules are not shared automatically across the app. If you want to use a provider from one module in another module, you need to:
Export it from the source module.
Import the source module into the destination module.
@Module({
providers: [CatsService],
exports: [CatsService], // 👈 This makes it available to other modules
})
export class CatsModule {}
In our CatsModule, we’ve created a service (CatsService) and a controller (CatsController). Here's the basic module structure:
import { Module } from '@nestjs/common';
import { CatsService } from './cats.service';
import { CatsController } from './cats.controller';
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {}
Now, if we run the application, you will notice the following:
NestJS first creates an instance of CatsService.
Then it creates an instance of CatsController.
This happens because CatsController depends on CatsService. So NestJS resolves dependencies from the bottom up. If a service depends on another service, that chain is resolved from the lowest level first ensuring everything is ready before the controller is instantiated.
Here’s how we inject the CatsService into the controller:
constructor(private readonly catsService: CatsService) {
console.log('CatsController instance created');
}
This is standard NestJS Dependency Injection (DI). The framework automatically injects the service instance into the controller.
When you add a service to the providers array of a module, NestJS creates a single instance (singleton) of that service:
This single instance is shared across all controllers in the same module.
That’s the core idea of the singleton pattern one shared instance per provider per module.
So, even if multiple controllers depend on CatsService, they will all receive the same instance.
Now, let’s say you try to use CatsService in another controller (e.g., in a RequestController inside RequestModule) like this:
constructor(private readonly catsService: CatsService) {}
If you run the application without importing CatsModule, you’ll get an error like:
Nest can't resolve dependencies of the RequestController (...) CatsService at index [0]
This means that CatsService is not available in the context of RequestModule.
NestJS modules are self contained by default. The scope of a provider is limited to the module where it is declared, unless you explicitly export it.
To fix the above error follow these steps:
Step 1: Export the Service from CatsModule:
@Module({
controllers: [CatsController],
providers: [CatsService],
exports: [CatsService], // 👈 This allows other modules to use it
})
export class CatsModule {}
Step 2: Import CatsModule into RequestModule:
import { Module } from '@nestjs/common';
import { RequestController } from './request.controller';
import { CatsModule } from 'src/cats/cats.module';
@Module({
controllers: [RequestController],
imports: [CatsModule], // 👈 Import the whole module, not just the service
})
export class RequestModule {}
Now NestJS can resolve the dependency, and CatsService will be available in RequestController.
NestJS resolves dependencies from bottom to top resolving providers before controllers.
Providers in NestJS are singleton by default shared within the module.
A provider’s scope is limited to its module unless it is exported.
To use a provider in another module:
Export it from the source module.
Import the entire module (not just the provider) into the destination module.
This behaviour makes modules self-contained and reusable.
Wrapping Up
We now understand how modules encapsulate their providers and controllers, how singleton services work, and how to properly share services between modules using exports and imports. In the future blog, we’ll explore custom provider scopes like Request, Transient, and how to build global modules.
If you’re a visual learner, check out the following video tutorial for this blog:
https://www.youtube.com/watch?v=D83bP3mfXWE&list=PLgKb0PcT0J0SaWTrS4ZY8ATMaqLeSXgM7&index=11
Follow us for more such content:
https://www.linkedin.com/company/hasabtech
https://www.youtube.com/@hasabTech



