Dwex Logo

Modules

Organize your application into cohesive, reusable modules

What are Modules?

Modules are the fundamental building blocks of a Dwex application. A module is a class decorated with @Module() that organizes related components (controllers, providers, etc.) into cohesive units.

Basic Module

Here's a simple module:

import { Module } from "@dwex/core";
import { UserController } from "./user.controller";
import { UserService } from "./user.service";

@Module({
  controllers: [UserController],
  providers: [UserService],
})
export class UserModule {}

Module Metadata

The @Module() decorator accepts an object with the following properties:

controllers

Controllers defined in this module. These handle incoming HTTP requests.

@Module({
  controllers: [UserController, ProfileController],
})
export class UserModule {}

providers

Injectable services and other providers available in this module.

@Module({
  providers: [UserService, EmailService, ValidationPipe],
})
export class UserModule {}

imports

Other modules whose exported providers should be available in this module.

@Module({
  imports: [DatabaseModule, LoggerModule],
  controllers: [UserController],
  providers: [UserService],
})
export class UserModule {}

exports

Providers that should be available to other modules that import this module.

@Module({
  providers: [UserService, UserRepository],
  exports: [UserService], // UserRepository is private to this module
})
export class UserModule {}

Importing Modules

To use providers from another module, import that module:

@Module({
  imports: [UserModule], // Imports UserService
  controllers: [PostController],
  providers: [PostService],
})
export class PostModule {}

Now PostService can inject UserService:

@Injectable()
export class PostService {
  constructor(private readonly userService: UserService) {}

  async createPost(userId: string, title: string) {
    const user = await this.userService.findOne(userId);
    // ...
  }
}

Global Modules

If a module should be available everywhere without explicit imports, mark it as global:

import { Module, Global } from "@dwex/core";

@Global()
@Module({
  providers: [ConfigService, CacheService],
  exports: [ConfigService, CacheService],
})
export class SharedModule {}

Now any module can inject ConfigService or CacheService without importing SharedModule.

Best Practice: Only make truly shared utilities global. Overusing global modules can make dependencies unclear.

Dynamic Modules

Dynamic modules allow configuration at runtime. This is useful for modules that need different settings in different contexts.

Creating a Dynamic Module

import { Module, type DynamicModule } from "@dwex/core";

@Module({})
export class DatabaseModule {
  static forRoot(options: DatabaseOptions): DynamicModule {
    return {
      module: DatabaseModule,
      providers: [
        {
          provide: "DATABASE_OPTIONS",
          useValue: options,
        },
        DatabaseService,
      ],
      exports: [DatabaseService],
    };
  }
}

Using a Dynamic Module

@Module({
  imports: [
    DatabaseModule.forRoot({
      host: "localhost",
      port: 5432,
      database: "myapp",
    }),
  ],
})
export class AppModule {}

Async Configuration

For configuration that requires async initialization:

@Module({})
export class DatabaseModule {
  static async forRootAsync(
    options: DatabaseAsyncOptions
  ): Promise<DynamicModule> {
    const config = await options.useFactory();

    return {
      module: DatabaseModule,
      providers: [
        {
          provide: "DATABASE_OPTIONS",
          useValue: config,
        },
        DatabaseService,
      ],
      exports: [DatabaseService],
    };
  }
}

Usage:

@Module({
  imports: [
    await DatabaseModule.forRootAsync({
      useFactory: async () => {
        const config = await loadConfig();
        return config.database;
      },
    }),
  ],
})
export class AppModule {}

Module Re-exporting

You can re-export imported modules to simplify imports:

@Module({
  imports: [UserModule, PostModule, CommentModule],
  exports: [UserModule, PostModule, CommentModule],
})
export class ContentModule {}

Now other modules only need to import ContentModule:

@Module({
  imports: [ContentModule], // Gets User, Post, and Comment modules
})
export class AppModule {}

Feature Modules

Organize your app by features:

// user.module.ts
@Module({
  imports: [DatabaseModule],
  controllers: [UserController],
  providers: [UserService, UserRepository],
  exports: [UserService],
})
export class UserModule {}
// auth.module.ts
@Module({
  imports: [UserModule, JwtModule],
  controllers: [AuthController],
  providers: [AuthService, AuthGuard],
  exports: [AuthGuard],
})
export class AuthModule {}
// app.module.ts
@Module({
  imports: [UserModule, AuthModule],
})
export class AppModule {}

Root Module

Every application has exactly one root module passed to DwexFactory.create():

import { DwexFactory } from "@dwex/core";
import { AppModule } from "./app.module";

const app = await DwexFactory.create(AppModule);

The root module typically imports all feature modules:

@Module({
  imports: [
    LoggerModule,
    DatabaseModule.forRoot({ url: process.env.DATABASE_URL }),
    UserModule,
    AuthModule,
    PostModule,
  ],
})
export class AppModule {}

Best Practices

1. Single Responsibility

Each module should focus on one feature or domain:

// Good
@Module({ ... })
export class UserModule {}

@Module({ ... })
export class AuthModule {}

// Avoid
@Module({ ... })
export class UserAndAuthModule {} // Too broad

2. Export Only What's Needed

Keep module internals private:

@Module({
  providers: [UserService, UserRepository, UserValidator],
  exports: [UserService], // Only expose the service
})
export class UserModule {}

3. Use Shared Modules Wisely

Create a SharedModule for truly common utilities:

@Global()
@Module({
  providers: [ConfigService, LoggerService],
  exports: [ConfigService, LoggerService],
})
export class SharedModule {}

4. Lazy Loading (Future)

While not yet implemented, plan your modules for potential lazy loading:

// Each feature in its own module
@Module({ ... })
export class AdminModule {}

@Module({ ... })
export class PublicModule {}

Next Steps