title: 'Modules · Nael Framework' description: 'Learn how to organize your Nael application using modules for better code structure and reusability.'
Modules
A module is a class annotated with a @Module() decorator. The @Module() decorator provides metadata that Nael makes use of to organize the application structure. Each application has at least one module, a root module. The root module is the starting point Nael uses to build the application graph.
Feature modules
The UsersController and UsersService belong to the same application domain. As they are closely related, it makes sense to move them into a feature module. A feature module simply organizes code relevant for a specific feature, keeping code organized and establishing clear boundaries. This helps us manage complexity and develop with SOLID principles, especially as the size of the application grows.
// users/users.module.tsimport { Module } from '@nl-framework/core';@Module({ controllers: [UsersController], providers: [UsersService],})export class UsersModule {}We created users.module.ts and moved everything related to this module into the users directory. The last thing we need to do is import this module into the root module (the AppModule, defined in the app.module.ts file).
Shared modules
In Nael, modules are singletons by default, and thus you can share the same instance of any provider between multiple modules effortlessly. Every module is automatically a shared module. Once created it can be reused by any module. Let's imagine that we want to share an instance of the ConfigService between several other modules.
import { Module } from '@nl-framework/core';@Module({ providers: [ConfigService], exports: [ConfigService],})export class SharedModule {}Now any module that imports the SharedModule has access to the ConfigService and will share the same instance with all other modules that import it as well.
Module re-exporting
As seen above, Modules can export their internal providers. In addition, they can re-export modules that they import. In the example below, the ConfigModule is both imported into and exported from the CoreModule, making it available for other modules which import this one.
import { Module } from '@nl-framework/core';@Module({ imports: [ConfigModule, DatabaseModule], exports: [ConfigModule, DatabaseModule],})export class CoreModule {}Dependency injection
A module class can inject providers as well (e.g., for configuration purposes):
import { Module } from '@nl-framework/core';@Module({ controllers: [UsersController], providers: [UsersService],})export class UsersModule { constructor(private authService: AuthService) {}}However, module classes themselves cannot be injected as providers due to circular dependency concerns.
Dynamic modules
The Nael module system includes a powerful feature called dynamic modules. This feature enables you to easily create customizable modules that can register and configure providers dynamically. Dynamic modules are covered extensively in the Fundamentals chapter.
Following is an example of a dynamic module definition for a ConfigModule:
import { Module, DynamicModule } from '@nl-framework/core';@Module({})export class ConfigModule { static forRoot(options: ConfigOptions): DynamicModule { return { module: ConfigModule, providers: [ { provide: 'CONFIG_OPTIONS', useValue: options, }, ConfigService, ], exports: [ConfigService], }; }}This module defines the ConfigService provider by default, but additionally - depending on the options object passed - exposes a collection of providers. Note that the properties of the object returned by the dynamic module extend (rather than override) the base module metadata defined in the @Module() decorator.
import { Module } from '@nl-framework/core';@Module({ imports: [ ConfigModule.forRoot({ folder: './config', }), ],})export class AppModule {}Async configuration
Sometimes you may want to pass module options asynchronously instead of statically. In this case, use the forRootAsync() method, which provides several ways to deal with async configuration.
import { Module, DynamicModule } from '@nl-framework/core';@Module({})export class DatabaseModule { static forRootAsync(options: { useFactory: (...args: any[]) => Promise<DatabaseOptions>; inject?: any[]; }): DynamicModule { return { module: DatabaseModule, providers: [ { provide: 'DATABASE_OPTIONS', useFactory: options.useFactory, inject: options.inject || [], }, { provide: DatabaseConnection, useFactory: async (dbOptions) => { const connection = await createConnection(dbOptions); return connection; }, inject: ['DATABASE_OPTIONS'], }, DatabaseService, ], exports: [DatabaseService], }; }}One approach is to use a factory function:
import { Module } from '@nl-framework/core';@Module({ imports: [ DatabaseModule.forRootAsync({ useFactory: (config: ConfigService) => ({ host: config.get('DB_HOST'), port: config.get('DB_PORT'), }), inject: [ConfigService], }), ],})export class AppModule {}Our factory function can be async and can inject dependencies through inject. In this case, the ConfigService will be injected and used to retrieve the database configuration.
Module structure
Here's a recommended directory structure for organizing your modules:
src/ app.module.ts main.ts users/ dto/ create-user.dto.ts update-user.dto.ts entities/ user.entity.ts users.controller.ts users.module.ts users.service.ts auth/ auth.controller.ts auth.module.ts auth.service.ts guards/ jwt.guard.ts strategies/ jwt.strategy.ts common/ decorators/ roles.decorator.ts guards/ roles.guard.ts interceptors/ logging.interceptor.tsThis structure helps maintain clean separation of concerns, with each module containing its own controllers, services, DTOs, and entities. Common functionality can be shared through a common directory.
Module metadata
The @Module() decorator takes a single object whose properties describe the module:
providers- the providers that will be instantiated by the Nael injector and that may be shared at least across this modulecontrollers- the set of controllers defined in this module which have to be instantiatedimports- the list of imported modules that export the providers which are required in this moduleexports- the subset ofprovidersthat are provided by this module and should be available in other modules which import this module. You can use either the provider itself or just its token (providevalue)