This chapter follows What is NestJS.
An ES import makes a class visible to the compiler. It does not tell Nest to create an instance or to hand one to another class. That relationship is declared separately, in modules. A module is a class with a @Module() decorator whose metadata lists what belongs to it and what it needs from elsewhere. Nest reads every module at startup and builds one application graph from them, which it then uses to resolve every injection.
What a module declares
nest g module cats creates an empty module. Filled in, it has up to four keys:
import { Module } from '@nestjs/common';import { UsersModule } from '../users/users.module';import { CatsController } from './cats.controller';import { CatsService } from './cats.service';
@Module({ imports: [UsersModule], controllers: [CatsController], providers: [CatsService], exports: [CatsService],})export class CatsModule {}| Key | Meaning |
|---|---|
imports |
Modules whose exported providers this module may inject |
controllers |
Controllers Nest should create and register routes for |
providers |
Providers Nest should create, visible inside this module |
exports |
The subset of providers (or imported modules) that importers may use |
A provider is private to its module unless it is exported. Listing a class under providers tells Nest to create it; listing it under exports lets other modules see it.
The root module and feature modules
Every application has one root module, AppModule by convention, which main.ts passes to NestFactory.create. Everything reachable from its imports becomes part of the app; a module that nothing imports is never loaded.
Everything else is a feature module: a folder per business area (cats, users, orders) holding that area’s controller, service, DTOs, and entities. The root module mostly imports feature modules and infrastructure such as configuration and the database connection:
@Module({ imports: [ConfigModule.forRoot({ isGlobal: true }), UsersModule, CatsModule],})export class AppModule {}Grouping by feature, not by kind (all controllers in one folder, all services in another), keeps a change to one area inside one folder and makes the dependencies between areas visible in the imports arrays.
Sharing providers between modules
Modules are singletons. When both CatsModule and OrdersModule import UsersModule, they get the same UsersService instance. There is no extra step to share a provider: export it from the module that owns it and import that module where it is needed.
The usual failure looks like this at startup:
Nest can't resolve dependencies of the CatsService (?). Please make sure that the argumentUsersService at index [0] is available in the CatsModule context.The fix is almost always one of two things: UsersModule does not list UsersService in exports, or CatsModule does not list UsersModule in imports. Adding UsersService to the providers of CatsModule instead is a mistake, because it creates a second, separate instance.
Re-exporting modules
A module can export a module it imports. Importers then get that module’s exports too. This lets a CoreModule bundle infrastructure that most features need:
@Module({ imports: [DatabaseModule, LoggingModule], exports: [DatabaseModule, LoggingModule],})export class CoreModule {}A feature module that imports CoreModule can now inject whatever DatabaseModule and LoggingModule export. Use it for a stable set of infrastructure, not to hide feature-to-feature dependencies, which should stay explicit.
Global modules
@Global() makes a module’s exports injectable everywhere without an import. The module still has to be imported once, usually in the root module.
import { Global, Module } from '@nestjs/common';import { AppLogger } from './app-logger';
@Global()@Module({ providers: [AppLogger], exports: [AppLogger],})export class LoggingModule {}Global modules remove the dependency from the imports arrays, which is exactly what makes them easy to overuse: you can no longer see from a module’s metadata what it relies on. Keep them for true infrastructure that nearly every module needs, such as configuration or logging. Many library modules offer the same effect through an option, for example ConfigModule.forRoot({ isGlobal: true }). How such forRoot() methods work is the subject of Dynamic modules in NestJS.
Next: Controllers in NestJS.
Check your understanding
Question 1 of 5
0 of 5 checked