Suggest an editImprove this articleRefine the answer for “What are modules in NestJS and how do they work?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**NestJS module** is a TypeScript class with `@Module()` that groups related controllers and providers into a DI scope. Providers are private by default - only those listed in `exports` are available to other modules. ```typescript @Module({ controllers: [UsersController], providers: [UsersService], exports: [UsersService] }) export class UsersModule {} ``` **Key point:** importing a module without `exports` gives other modules nothing - you get `No provider for X` at startup.Shown above the full answer for quick recall.Answer (EN)Image**NestJS module** is a TypeScript class decorated with `@Module()` that groups related controllers, providers, and sub-modules into a dependency injection scope. ## Theory ### TL;DR - Modules are like shipping containers: each holds its cargo (controllers, providers), declares what it needs from others (`imports`), and what it shares outward (`exports`) - Providers in a module are private by default - nothing leaks out unless you explicitly list it in `exports` - One feature = one module (`UsersModule`, `OrdersModule`). `AppModule` just imports them - `providers` registers a class in this module's DI scope; `exports` makes a subset available to importers - Circular imports need `forwardRef()` ### Quick example ```typescript // users/users.module.ts @Module({ controllers: [UsersController], // handles /users routes providers: [UsersService], // scoped to this module only exports: [UsersService], // other modules can now inject this }) export class UsersModule {} // app.module.ts @Module({ imports: [UsersModule], // pulls in UsersModule's exports }) export class AppModule {} ``` NestJS scans `AppModule`, loads `UsersModule`, and wires `UsersService` into `UsersController` at startup. No manual instantiation. The `/users` endpoints go live. ### Dependency scope Providers live only inside the module that declares them. If `EmailsModule` needs `UsersService`, it must import `UsersModule`, and `UsersModule` must export `UsersService`. That contract is explicit and checked at startup, not at request time. Compare this to plain Express, where you `require()` anything from anywhere. Fine at small scale, hard to trace at large scale. NestJS makes ownership visible. ### Module types **Feature module** groups one domain area. It has its own controllers and services, and exports what other modules might need. **Shared module** provides common utilities across the app. Put `DatabaseService`, `EmailService`, or `LoggerService` here and export them. Any feature module that imports `SharedModule` gets access. **Global module** (decorated with `@Global()`) registers providers app-wide, so no module needs to explicitly import it. Useful for config or logging. But used widely, it hides dependencies - you can no longer tell from a module's `imports` what it actually uses. **Dynamic module** accepts runtime configuration. `TypeOrmModule.forRoot(options)` is the classic example. The static method returns a `DynamicModule` object instead of a class, letting you pass database credentials, API keys, or file paths at import time. ### How the module graph is built At startup, NestJS reads all `@Module()` decorators and builds a directed acyclic graph (DAG) of dependencies. It resolves `imports` recursively until the full tree is mapped. Then it instantiates providers using TypeScript metadata reflection via `reflect-metadata`. The IoC container matches `@Inject()` tokens to providers within the resolved scope. If a token is missing, you get `UnknownInjectionToken` before any request is handled. That early failure is intentional - you find broken wiring at boot, not in production at 3am. ### Dynamic module pattern in practice ```typescript // config/config.module.ts import { DynamicModule, Module } from '@nestjs/common'; import { ConfigService } from './config.service'; @Module({}) export class ConfigModule { static forRoot(configPath: string): DynamicModule { return { module: ConfigModule, global: true, // injectable everywhere, no explicit imports needed providers: [ { provide: 'CONFIG_PATH', useValue: configPath }, ConfigService, ], exports: [ConfigService], }; } } // app.module.ts @Module({ imports: [ConfigModule.forRoot('prod.env')], }) export class AppModule {} ``` `global: true` makes `ConfigService` injectable everywhere. Without it, only modules that explicitly import `ConfigModule` can use the service. Both approaches work - choose based on how widely the service is needed. ### Common mistakes **Forgetting `exports`** is the most common error, especially after moving a service to a new module: ```typescript // users.module.ts - WRONG: no exports @Module({ providers: [UsersService] }) export class UsersModule {} // emails.module.ts imports UsersModule, but: constructor(private usersService: UsersService) {} // Error: Nest can't resolve dependencies of EmailsService. // No provider for UsersService! ``` Fix: add `exports: [UsersService]` to `UsersModule`. Importing a module alone gives you nothing. **Circular dependencies** occur when `UsersModule` imports `EmailsModule` and `EmailsModule` imports `UsersModule`. NestJS throws a circular dependency error. Fix with `forwardRef()`: ```typescript // users.module.ts @Module({ imports: [forwardRef(() => EmailsModule)], }) export class UsersModule {} ``` **Dumping everything into AppModule** is the antipattern I see most in junior codebases. 30-50 providers in the root module means you can't test anything in isolation and refactoring becomes painful. AppModule should import feature modules, not declare services directly. **Overusing `global: true`** because "my service isn't injecting." It works, but it creates invisible coupling. Export from the declaring module and import where needed instead. ### Real-world usage - `nest new` scaffolds `AppModule` importing feature modules from day one - `@nestjs/passport` wraps authentication as `PassportModule` with standard import syntax - Prisma integration lives in a shared `PrismaModule` exported to all features that need DB access - `TypeOrmModule.forRoot()` at the root and `TypeOrmModule.forFeature([Entity])` in each feature module is the canonical dynamic module pattern - Microservice transport modules (TCP, Redis, Kafka) use the same `@Module()` structure, just with different controller decorators ### Follow-up questions **Q:** What is the difference between `providers` and `exports`? **A:** `providers` registers classes in this module's DI scope. `exports` makes a subset of those providers available to other modules that import this one. A provider not in `exports` stays private. **Q:** How does NestJS resolve injection tokens? **A:** Via the class itself (most common) or a custom string/symbol token used with `@Inject('TOKEN')`. NestJS matches the token to a provider in the module graph and throws `UnknownInjectionToken` if it finds nothing. **Q:** When should you use `global: true` on a module? **A:** For app-wide singletons that every module genuinely needs, like config or logging. Avoid it for domain services - explicit imports make dependencies traceable. **Q:** How do modules help with testing? **A:** In unit tests, you create a `TestingModule` and replace real imports with mocks. The module boundary means you only need to provide the exact dependencies of the class you are testing, nothing else. **Q:** Build a dynamic module that loads database config asynchronously using `forRootAsync`. **A:** Use `useFactory` with async support inside the returned `DynamicModule`. The factory receives injected dependencies (like `ConfigService`) via `inject` and returns the config object. NestJS waits for the promise before completing module setup and accepting requests. ## Examples ### Feature module: user endpoints ```typescript // users/users.module.ts import { Module } from '@nestjs/common'; import { UsersController } from './users.controller'; import { UsersService } from './users.service'; @Module({ controllers: [UsersController], providers: [UsersService], exports: [UsersService], // AuthModule will need this to verify users }) export class UsersModule {} // users/users.service.ts import { Injectable } from '@nestjs/common'; @Injectable() export class UsersService { private users = [{ id: 1, name: 'Alice' }]; findById(id: number) { return this.users.find(u => u.id === id); } } // users/users.controller.ts import { Controller, Get, Param, ParseIntPipe } from '@nestjs/common'; @Controller('users') export class UsersController { constructor(private readonly usersService: UsersService) {} @Get(':id') getUser(@Param('id', ParseIntPipe) id: number) { return this.usersService.findById(id); // DI handles the wiring } } ``` `UsersService` is declared in `UsersModule` and injected into `UsersController` with no manual `new UsersService()`. The controller just lists the dependency in its constructor. ### Shared module for database access ```typescript // database/database.module.ts import { Module } from '@nestjs/common'; import { DatabaseService } from './database.service'; @Module({ providers: [DatabaseService], exports: [DatabaseService], // any importing module gets access }) export class DatabaseModule {} // users/users.module.ts import { Module } from '@nestjs/common'; import { DatabaseModule } from '../database/database.module'; import { UsersService } from './users.service'; import { UsersController } from './users.controller'; @Module({ imports: [DatabaseModule], // pulls in DatabaseService controllers: [UsersController], providers: [UsersService], // UsersService can now inject DatabaseService }) export class UsersModule {} // users/users.service.ts @Injectable() export class UsersService { constructor(private readonly db: DatabaseService) {} findAll() { return this.db.query('SELECT * FROM users'); } } ``` `DatabaseService` is defined once and shared across `UsersModule`, `OrdersModule`, or any feature that imports `DatabaseModule`. One definition, multiple consumers. ### Advanced: dynamic module with async factory ```typescript // config/config.module.ts import { DynamicModule, Module } from '@nestjs/common'; import { ConfigService } from './config.service'; @Module({}) export class ConfigModule { static forRootAsync(options: { useFactory: (...args: any[]) => Promise<Record<string, string>>; inject?: any[]; }): DynamicModule { return { module: ConfigModule, global: true, providers: [ { provide: 'CONFIG_OPTIONS', useFactory: options.useFactory, inject: options.inject || [], }, ConfigService, ], exports: [ConfigService], }; } } // app.module.ts @Module({ imports: [ ConfigModule.forRootAsync({ useFactory: async () => ({ DB_HOST: process.env.DB_HOST ?? 'localhost', JWT_SECRET: process.env.JWT_SECRET ?? 'dev-secret', }), }), ], }) export class AppModule {} ``` The factory runs async before the app accepts any requests. `ConfigService` is available everywhere because `global: true`. This is how `@nestjs/config` works internally.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.