modules

SKILL.md

NestJS Modules

When to Use This Skill

Use this skill when:

  • Organizing application structure into features
  • Creating reusable modules
  • Configuring module imports and exports
  • Creating dynamic modules with runtime configuration
  • Setting up global modules
  • Understanding module dependency graph

What are Modules?

Modules are classes decorated with @Module() that organize the application structure. Every application has at least one root module, and feature modules organize related functionality.

Basic Module

import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {}

Module Decorator Properties

@Module({
  imports: [],      // Other modules whose providers are needed
  controllers: [],  // Controllers to instantiate
  providers: [],    // Providers to instantiate and make available
  exports: [],      // Providers to make available to other modules
})

Root Module

Every application has a root module:

// app.module.ts
import { Module } from '@nestjs/common';
import { CatsModule } from './cats/cats.module';
import { DogsModule } from './dogs/dogs.module';

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

// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();

Feature Modules

Organize related features:

// users/users.module.ts
@Module({
  imports: [TypeOrmModule.forFeature([User])],
  controllers: [UsersController],
  providers: [UsersService, UsersRepository],
  exports: [UsersService], // Available to other modules
})
export class UsersModule {}

// app.module.ts
@Module({
  imports: [UsersModule, AuthModule, ProductsModule],
})
export class AppModule {}

Shared Modules

Modules are singletons by default - providers are shared:

// database/database.module.ts
@Module({
  providers: [DatabaseService],
  exports: [DatabaseService],
})
export class DatabaseModule {}

// Multiple modules can import it
@Module({
  imports: [DatabaseModule],
})
export class UsersModule {}

@Module({
  imports: [DatabaseModule],
})
export class ProductsModule {}

Both modules share the same DatabaseService instance.

Module Re-exporting

Re-export imported modules:

// common.module.ts
@Module({
  imports: [LoggerModule, ConfigModule],
  exports: [LoggerModule, ConfigModule], // Re-export
})
export class CommonModule {}

// app.module.ts
@Module({
  imports: [CommonModule], // Gets Logger and Config
})
export class AppModule {}

Global Modules

Make a module available everywhere without importing:

import { Module, Global } from '@nestjs/common';

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

Usage:

// No need to import ConfigModule
@Injectable()
export class AnyService {
  constructor(private config: ConfigService) {}
}

Best Practices:

  • Use sparingly (only for truly global services)
  • Prefer explicit imports for better clarity
  • Good for: config, logging, caching services

Dynamic Modules

Create modules with runtime configuration:

// database.module.ts
import { Module, DynamicModule } from '@nestjs/common';

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

// Usage
@Module({
  imports: [
    DatabaseModule.forRoot({
      host: 'localhost',
      port: 5432,
    }),
  ],
})
export class AppModule {}

Dynamic Module Patterns

forRoot Pattern

For root-level configuration (singleton):

@Module({})
export class ConfigModule {
  static forRoot(options: ConfigOptions): DynamicModule {
    return {
      module: ConfigModule,
      providers: [
        {
          provide: 'CONFIG_OPTIONS',
          useValue: options,
        },
        ConfigService,
      ],
      exports: [ConfigService],
      global: true, // Optional: make it global
    };
  }
}

forFeature Pattern

For feature-specific configuration:

@Module({})
export class TypeOrmModule {
  static forFeature(entities: Entity[]): DynamicModule {
    const providers = entities.map(entity => ({
      provide: getRepositoryToken(entity),
      useFactory: (connection) => connection.getRepository(entity),
      inject: ['DATABASE_CONNECTION'],
    }));

    return {
      module: TypeOrmModule,
      providers,
      exports: providers,
    };
  }
}

// Usage
@Module({
  imports: [TypeOrmModule.forFeature([User, Product])],
})
export class AppModule {}

forRootAsync Pattern

For async configuration with dependency injection:

@Module({})
export class ConfigModule {
  static forRootAsync(options: ConfigAsyncOptions): DynamicModule {
    return {
      module: ConfigModule,
      imports: options.imports || [],
      providers: [
        {
          provide: 'CONFIG_OPTIONS',
          useFactory: options.useFactory,
          inject: options.inject || [],
        },
        ConfigService,
      ],
      exports: [ConfigService],
    };
  }
}

// Usage
@Module({
  imports: [
    ConfigModule.forRootAsync({
      imports: [EnvModule],
      useFactory: (env: EnvService) => ({
        apiKey: env.get('API_KEY'),
      }),
      inject: [EnvService],
    }),
  ],
})
export class AppModule {}

ConfigurableModuleBuilder

Simplify dynamic module creation:

import { ConfigurableModuleBuilder } from '@nestjs/common';

interface ConfigOptions {
  apiKey: string;
  timeout: number;
}

export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } =
  new ConfigurableModuleBuilder<ConfigOptions>()
    .setClassMethodName('forRoot')
    .build();

@Module({})
export class ConfigModule extends ConfigurableModuleClass {}

// Automatically creates forRoot() and forRootAsync()
// Usage
@Module({
  imports: [
    ConfigModule.forRoot({
      apiKey: 'key',
      timeout: 5000,
    }),
  ],
})
export class AppModule {}

Module Dependency Injection

Inject providers from imported modules:

// database.module.ts
@Module({
  providers: [DatabaseService],
  exports: [DatabaseService],
})
export class DatabaseModule {}

// users.module.ts
@Module({
  imports: [DatabaseModule],
  providers: [UsersService],
})
export class UsersModule {}

// users.service.ts
@Injectable()
export class UsersService {
  constructor(private db: DatabaseService) {} // Injected from DatabaseModule
}

Circular Dependencies Between Modules

Use forwardRef():

// cats.module.ts
@Module({
  imports: [forwardRef(() => DogsModule)],
  providers: [CatsService],
  exports: [CatsService],
})
export class CatsModule {}

// dogs.module.ts
@Module({
  imports: [forwardRef(() => CatsModule)],
  providers: [DogsService],
  exports: [DogsService],
})
export class DogsModule {}

Module Organization Patterns

By Feature

src/
├── users/
│   ├── users.module.ts
│   ├── users.controller.ts
│   ├── users.service.ts
│   └── dto/
├── products/
│   ├── products.module.ts
│   ├── products.controller.ts
│   └── products.service.ts
└── app.module.ts

By Layer

src/
├── controllers/
├── services/
├── repositories/
├── modules/
└── app.module.ts

Hybrid (Recommended)

src/
├── modules/
│   ├── users/
│   │   ├── users.module.ts
│   │   ├── users.controller.ts
│   │   ├── users.service.ts
│   │   └── dto/
│   └── products/
├── common/
│   ├── guards/
│   ├── interceptors/
│   └── pipes/
└── app.module.ts

Complete Example

// config/config.module.ts
@Global()
@Module({})
export class ConfigModule {
  static forRoot(options: ConfigOptions): DynamicModule {
    return {
      module: ConfigModule,
      providers: [
        {
          provide: 'CONFIG_OPTIONS',
          useValue: options,
        },
        ConfigService,
      ],
      exports: [ConfigService],
    };
  }
}

// database/database.module.ts
@Module({})
export class DatabaseModule {
  static forRootAsync(): DynamicModule {
    return {
      module: DatabaseModule,
      providers: [
        {
          provide: 'DATABASE_CONNECTION',
          useFactory: async (config: ConfigService) => {
            return createConnection(config.database);
          },
          inject: [ConfigService],
        },
        DatabaseService,
      ],
      exports: [DatabaseService],
    };
  }
}

// users/users.module.ts
@Module({
  imports: [
    DatabaseModule.forFeature([User]),
  ],
  controllers: [UsersController],
  providers: [UsersService, UsersRepository],
  exports: [UsersService],
})
export class UsersModule {}

// app.module.ts
@Module({
  imports: [
    ConfigModule.forRoot({
      envPath: '.env',
    }),
    DatabaseModule.forRootAsync(),
    UsersModule,
    AuthModule,
  ],
})
export class AppModule {}

Best Practices

  1. One module per feature - Keep related code together
  2. Export selectively - Only export what's needed by other modules
  3. Use feature modules - Don't put everything in AppModule
  4. Shared modules - Create common modules for shared functionality
  5. Global sparingly - Only for truly global services
  6. Dynamic for configuration - Use for libraries and configurable modules
  7. Avoid circular dependencies - Refactor to shared module if needed
  8. Clear naming - UsersModule, AuthModule, not Module1
  9. Keep modules focused - Single responsibility per module
  10. Document dependencies - Make imports explicit

CLI Commands

# Generate a module
nest generate module users

# Generate a complete resource (module + controller + service)
nest generate resource users

# Generate module with CRUD
nest generate resource users --no-spec
Weekly Installs
1
GitHub Stars
1
First Seen
6 days ago
Installed on
amp1
cline1
opencode1
cursor1
kimi-cli1
warp1