Fullstack CourseLearn by building
Back to week 1

Topic

Modules

Definition

A Nest module is a class annotated with @Module that declares the controllers and providers belonging to a cohesive part of the application, and the imports/exports that make dependencies available across module boundaries.

In simpler words

A module is a labeled box: it says which controllers and services live here, what it needs from elsewhere, and what it is willing to share.

Every Nest feature — tickets, auth, health — is a module. AppModule is the root module that assembles the whole app. Read modules before controllers; they tell you what exists.

After this you can

  • Read the four @Module keys: imports, controllers, providers, exports
  • Explain TypeOrmModule.forFeature([Ticket]) inside TicketsModule
  • Trace how AppModule assembles Health/Users/Auth/Tickets modules
  • Know when a provider must be exported

@Module anatomy

Definition

The @Module decorator accepts a metadata object with optional imports, controllers, providers, and exports arrays that Nest reads to build the application’s dependency graph.

In simpler words

imports = other modules this one needs; controllers = routes it exposes; providers = services it owns; exports = which of its providers others may use.

Nest reads @Module metadata at bootstrap to construct a dependency-injection graph — it does not scan folders or filenames, only decorator metadata.

A provider not listed in providers cannot be injected here, even if the class exists in the same folder. A provider not listed in exports cannot be injected by a different module that imports this one.

TicketsModule (apps/api)

@Module({
  imports: [TypeOrmModule.forFeature([Ticket])],
  controllers: [TicketsController],
  providers: [TicketsService],
})
export class TicketsModule {}

forFeature([Ticket]) registers a TypeORM Repository<Ticket> that TicketsService can inject.

The root module wires the whole app

Definition

A root module is the single module passed to NestFactory.create that transitively imports every feature module the application needs.

In simpler words

AppModule is the top of the tree — if a module is not (directly or indirectly) imported by AppModule, Nest never loads it.

apps/api/src/app.module.ts imports ConfigModule, TypeOrmModule.forRoot(...), HealthModule, UsersModule, AuthModule, and TicketsModule — that list is the entire backend surface.

AppModule also implements NestModule to register CorrelationIdMiddleware across every route via configure(consumer) — middleware registration is a module-level concern, separate from providers/controllers.

AppModule imports (apps/api/src/app.module.ts)

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true }),
    TypeOrmModule.forRoot({ type: 'postgres', url: env.DATABASE_URL, synchronize: false /* ... */ }),
    HealthModule,
    UsersModule,
    AuthModule,
    TicketsModule,
  ],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(CorrelationIdMiddleware).forRoutes('*');
  }
}

Add a new feature module here and its controllers become reachable; forget this step and its routes 404.

Exporting providers across module boundaries

Definition

A module makes a provider available to importing modules by listing that provider in its exports array; the importing module must also import the exporting module.

In simpler words

To borrow a service from another module, that module must export it and your module must import it — both sides are required.

If AuthModule needs UsersService to look up credentials, UsersModule must export UsersService and AuthModule must import UsersModule.

Forgetting exports is a very common first bug: the provider exists and even compiles, but Nest throws a runtime "cannot resolve dependencies" error, because the DI container cannot see it outside its owning module.

Mistake: missing export

// Wrong — UsersService not exported
@Module({ providers: [UsersService] })
export class UsersModule {}

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

Without exports, importing UsersModule elsewhere still cannot inject UsersService.

Keep in mind

  • Read a feature by its module first: imports tell you dependencies, controllers/providers tell you what it does.
  • Circular module imports usually mean a boundary is drawn in the wrong place — extract a shared module.
  • Keep feature modules focused: tickets should not import unrelated modules “just in case”.

Test

Check your understanding

At least 10 questions — mix of concept, syntax, practical, and logic. Score ≥ 80% (enforced by the API) to save progress.

Checking your session…

10 questions · concept 3 · syntax 2 · practical 3 · logic 2

Concept1. Which statement best defines Nest modules?
Syntax2. Which code-level choice matches Nest modules?
Practical3. A reviewer spots a bug related to Nest modules. What is the right fix?
Logic4. Which reasoning correctly explains Nest modules?
Concept5. Which boundary does correct use of Nest modules preserve?
Practical6. What is the safest next step when applying Nest modules?
Syntax7. Which implementation direction matches the rule for Nest modules?
Logic8. Which consequence follows from applying Nest modules correctly?
Concept9. Which claim about Nest modules is true in this Nest + Postgres monorepo?
Practical10. Which team practice best demonstrates Nest modules?