CLOSE
Updated on 16 Apr, 202612 mins read 540 views

Why Modules Are the Most Important Concept in NestJS

In Express, structure is optional. In NestJS, structure is enforced.

Modules are the unit of architecture in NestJS.

Everything else – controllers, services, guards – lives inside a module.

Think of modules are:

Bounded contexts + dependency containers

What a Module Really Is

At runtime, a NestJS module is:

  • A dependency scope
  • A visibility boundary
  • A configuration unit
@Module({
imports: [],
providers: [],
controllers: [],
exports: [],
})
export class UserModule {}

This metadata is consumed during startup, not runtime.

Root Modules vs Feature Modules

Root Module (AppModule)

Every NestJS app has a root module which is the entry point of the application.

  • Entry point for module discovery
  • Composes the entire application
  • Contains no business logic

Feature Module

Feature modules organize code by domain.

  • Encapsulates a domain or capability
  • Own their controllers and services
  • Export only what others need

Module Structure

@Module({
  imports: [],       // other modules
  controllers: [],   // route handlers
  providers: [],     // services
  exports: [],       // shared providers
})

The imports Array – Dependency Declaration

When a module imports another module, it is saying:

I depend on the public API of that module.

Example:

@Module({
imports: [UserModule],
})
export class OrderModule {}

This does not mean:

  • Access to all providers
  • Access to private internals

Only exported providers are visible.

The providers Array – Ownership Matters

Providers declared in a module are:

  • Owned by that module
  • Instantiated in that module's scope
@Module({
providers: [UserService],
})
export class UserModule {}

Ownership implies:

  • Lifecycle responsibility
  • Initialization responsibility

The exports Array – Public API of a Module

Exports define what other modules are allowed to use.

@Module({
providers: [UserService],
exports: [UserService],
})
export class UserModule {}

Think of exports as:

The public interface of a module

Everything else is private.

Your First Module

Let's create a simple module.

Step 1: Generate the Module

# The magic command that creates everything for you
nest generate module books

# What this creates:
# - books/books.module.ts (the module file)
# - Updates app.module.ts to import the new module

Step 2: Look Inside the Generated Module

// books/books.module.ts - Your brand new module!
import { Module } from '@nestjs/common';

@Module({})  // Empty for now - we'll fill it up
export class BooksModule {}

It's empty. That's okay – Think of it as an empty box. Let's add some contents.

Connecting Modules Together

To use one module inside another:

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

Sharing Providers Between Modules

By default, providers are private to a module.

To share them:

Step 1: Export provider

@Module({
  providers: [UsersService],
  exports: [UsersService],
})
export class UsersModule {}

Step 2: Import Module

@Module({
  imports: [UsersModule],
})
export class OrdersModule {}

Now UsersService is usable in OrdersModule.

Global Modules

Some modules should be available everywhere.

Example:

import { Global, Module } from '@nestjs/common';

@Global()
@Module({
  providers: [ConfigService],
  exports: [ConfigService],
})
export class ConfigModule {}

No need to import it repeatedly.

Buy Me A Coffee

Leave a comment

Your email address will not be published. Required fields are marked *

Your experience on this site will be improved by allowing cookies Cookie Policy