InteractiveFrameworks

Modules

Declare a feature module that owns the cats code, import it from the root module, and export the service so another module shares the same instance.

What you'll learn

  • Declare a feature module that owns its controllers and providers
  • Import a module into the root module so its routes exist
  • Export a provider so a module that imports yours injects the same instance, and know why registering it twice is wrong

Every Nest application has at least one module, the root AppModule, and so far everything has lived in it. Real applications have many: one per feature, each owning its controllers and providers. Modules are how Nest organises the dependency graph, and they decide what is visible where. Without them, every provider would be reachable from everywhere, and a large application would have no boundaries at all.

A feature module

A feature module gathers everything about one part of the application. Here is one for orders:

@Module({
  controllers: [OrdersController],
  providers: [OrdersService],
  exports: [OrdersService],
})
export class OrdersModule {}

The four properties of @Module():

  • controllers: the controllers this module owns. Their routes exist once the module is reachable from the root.
  • providers: the providers this module owns. They can be injected by anything in this module.
  • exports: the subset of this module's providers that modules importing it may inject.
  • imports: other modules whose exports this module wants.

The folder convention, orders/orders.module.ts next to orders.controller.ts and orders.service.ts, is only a convention: Nest does not care where files live, only what the decorators say. Follow it anyway. nest generate produces it, and every Nest codebase you will read uses it.

Importing a module

A module's controllers only exist once the module is reachable from the root. AppModule reaches OrdersModule by listing it in imports:

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

Leave OrdersModule out of imports and nothing is wrong with the code: the application starts, and GET /orders is a 404, because the controller was never registered. Watch the console when you run. Nest logs every module it initialises and every route it maps, so a missing module shows up as a missing [InstanceLoader] line and a missing route as a missing [RouterExplorer] line.

Providers are scoped to their module

OrdersService is registered in OrdersModule, so classes in OrdersModule can inject it. A controller in AppModule cannot, by default:

Nest can't resolve dependencies of the AppController (?).
Please make sure that the argument OrdersService at index [0] is available in the AppModule context.

The fix is on the providing side. OrdersModule names the providers it shares in exports, and any module that imports it can then inject those. Nest does not create a second OrdersService for AppModule. It is the same instance, so state kept in the service is shared across the boundary too.

A provider that is not exported stays private to its module. That is a feature: it keeps a feature's internals from being reached from everywhere, the way private keeps a class's internals from being reached.

The mistake that looks like a fix

When Nest cannot resolve a provider, adding the class to the current module's own providers makes the error go away. It also creates a second instance of the service, private to that module, with its own state. A counter in one is not the counter in the other, and nothing warns you. When a provider belongs to another module, the answer is always exports there and imports here, never a second registration.

Global modules

A module needed by almost every other module, typically configuration or a database connection, can be decorated with @Global() and imported once at the root. Its exports then resolve everywhere without importing it again. Use it rarely; the point of modules is that dependencies are explicit.

Your task

The cats code has moved into cats/, but cats.module.ts declares nothing yet, and AppController in the root module wants to count cats through CatsService.

  1. Make CatsModule own CatsController and CatsService, and share the service.
  2. Import CatsModule into AppModule.

Run once after doing only the first half of step 1 and read what the console says about AppController. Then finish, and check that GET / counts the cat that POST /cats created: one service, shared.

When it fails

  • GET /cats is a 404: CatsController is not in any module Nest reached. Either CatsModule does not list it, or AppModule does not import CatsModule.
  • Nest can't resolve dependencies of the AppController (?): CatsService is not exported from CatsModule. Do not register it in AppModule to make the message go away.
  • GET / answers { "cats": 0 } after a cat was created: two instances of CatsService exist. See the section above.
  • Nest can't resolve dependencies of the CatsController (?): the service is missing from CatsModule's own providers.

Remember

  • A module owns controllers and providers; imports and exports connect modules.
  • A module's routes exist only when the module is reachable from the root.
  • A provider is private to its module until the module exports it. The importing module gets the same instance.
  • A second registration is a second instance. Export instead.
Stuck? Show a hint

CatsModule's @Module() needs controllers, providers and exports. AppModule's imports needs CatsModule. Without the import there are no /cats routes; without the export, AppController cannot receive CatsService, and putting CatsService into AppModule's providers would give it a second, empty instance.