Modules
The unit of organisation in Nest, and the thing that decides what can see what.
A module groups related code and declares its boundary. Everything in a Nest application belongs to exactly one module, and modules decide what is visible outside themselves.
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
@Module({
imports: [], // other modules whose exports we need
controllers: [UsersController],
providers: [UsersService], // constructible and injectable inside this module
exports: [UsersService], // ...and available to modules that import us
})
export class UsersModule {}The key rule: a provider is private to its module unless it is exported. If OrdersService needs UsersService, then UsersModule must export it and OrdersModule must import UsersModule. There is no ambient global scope.
That constraint is the point. It makes the dependency graph explicit and it means you can see, from one file, everything a feature depends on.
Feature modules#
The standard layout is one module per domain concept:
src/
app.module.ts
users/
users.module.ts
users.controller.ts
users.service.ts
dto/
entities/
orders/
orders.module.ts
...@Module({
imports: [UsersModule, OrdersModule],
})
export class AppModule {}Shared modules#
A module exporting a provider is a singleton across the whole application by default. Import it in two places and both get the same instance.
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class DatabaseModule {}Dynamic modules#
When a module needs configuration, use the forRoot / forRootAsync convention.
@Module({})
export class MailModule {
static forRoot(options: MailOptions): DynamicModule {
return {
module: MailModule,
providers: [
{ provide: MAIL_OPTIONS, useValue: options },
MailService,
],
exports: [MailService],
};
}
}@Module({
imports: [MailModule.forRoot({ apiKey: process.env.MAIL_KEY! })],
})
export class AppModule {}Global modules#
@Global() makes a module's exports available everywhere without importing it.
@Global()
@Module({ providers: [ConfigService], exports: [ConfigService] })
export class ConfigModule {}Circular dependencies#
Two modules importing each other is usually a design smell, but when it is genuinely needed:
@Module({ imports: [forwardRef(() => OrdersModule)] })
export class UsersModule {}Before reaching for forwardRef, ask whether the shared piece belongs in a third module that both import. That is the right answer about eighty percent of the time.
Common questions#
How granular should modules be?#
One per bounded domain concept, not one per file. UsersModule containing the controller, service, DTOs and entities is right. Splitting a service into its own module because it felt big is not.
Why does my provider say it cannot be resolved?#
Almost always one of two things: the provider is not in the providers array of any module in scope, or it is in another module that does not export it. The error message names the missing token and the module it was requested from — read both halves.
Is forwardRef bad?#
It is a signal, not a sin. It usually means two modules share a concept that wants extracting into a third. If you have more than one or two, look at the shape of your domain rather than adding more.
Get the NestJS agent pack
A battle-tested AGENTS.md, the review checklist, and the failure-mode cheat sheet for NestJS. One email, then occasional updates when the tooling shifts. No course pitch.
AGENTS.md now — no email needed.