Skip to main content

Modules

Modules organize commands and provide a way to structure your CLI application. This guide covers how to create and use modules effectively.

What is a Module?

A module is a class decorated with @CliModule that groups related commands together. Modules can also import other modules to compose larger applications.

Basic Module

The simplest module contains a list of commands:

import { CliModule } from '@navios/commander'
import { GreetCommand } from './greet.command'
import { VersionCommand } from './version.command'

@CliModule({
commands: [GreetCommand, VersionCommand],
})
export class AppModule {}

Module with Imports

Modules can import other modules to compose larger applications:

import { CliModule } from '@navios/commander'
import { UserModule } from './user.module'
import { DatabaseModule } from './database.module'

@CliModule({
imports: [UserModule, DatabaseModule],
})
export class AppModule {}

Module with Commands and Imports

Modules can have both their own commands and import other modules:

import { CliModule } from '@navios/commander'
import { GreetCommand } from './greet.command'
import { UserModule } from './user.module'

@CliModule({
commands: [GreetCommand],
imports: [UserModule],
})
export class AppModule {}

Service Overrides

Navios uses InjectionTokens for dependency injection, allowing you to override services using priority. The overrides option ensures that override service classes are imported so their @Injectable decorators execute and register with the DI system.

When to Use Overrides

Use overrides when you have service implementations that override other services using the same InjectionToken with a higher priority. These override classes are not "used" in the module - they're imported purely for side effects (decorator execution).

Example: Overriding a Service

// user.service.ts - Original service
import { Injectable, InjectionToken } from '@navios/di'

export const USER_SERVICE_TOKEN = InjectionToken.create<UserService>('UserService')

@Injectable({ token: USER_SERVICE_TOKEN, priority: 100 })
export class UserService {
getUsers() {
return ['Alice', 'Bob']
}
}

// user.service.override.ts - Override service with higher priority
import { Injectable } from '@navios/di'
import { USER_SERVICE_TOKEN } from './user.service'

@Injectable({ token: USER_SERVICE_TOKEN, priority: 200 })
export class OverrideUserService {
getUsers() {
return ['Charlie', 'David'] // Override implementation
}
}

// app.module.ts - Register the override
import { CliModule } from '@navios/commander'
import { OverrideUserService } from './user.service.override'

@CliModule({
commands: [UserCommand],
overrides: [OverrideUserService], // Ensures override is registered
})
export class AppModule {}

When UserService is injected using USER_SERVICE_TOKEN, the DI system will resolve OverrideUserService because it has the higher priority (200 > 100).

Override Validation

The framework automatically validates overrides during module loading:

  • Warns if an override class is not registered (missing @Injectable decorator)
  • Warns if an override doesn't have the highest priority (another service with higher priority is active)
  • Logs debug message when an override is successfully active

This helps catch configuration errors early and ensures your overrides are working as expected.

Organizing by Feature

Group related commands into feature modules:

User Module

import { CliModule } from '@navios/commander'
import { CreateUserCommand } from './commands/create-user.command'
import { DeleteUserCommand } from './commands/delete-user.command'
import { ListUsersCommand } from './commands/list-users.command'

@CliModule({
commands: [
CreateUserCommand,
DeleteUserCommand,
ListUsersCommand,
],
})
export class UserModule {}

Database Module

import { CliModule } from '@navios/commander'
import { MigrateCommand } from './commands/migrate.command'
import { SeedCommand } from './commands/seed.command'
import { ResetCommand } from './commands/reset.command'

@CliModule({
commands: [
MigrateCommand,
SeedCommand,
ResetCommand,
],
})
export class DatabaseModule {}

Root Module

import { CliModule } from '@navios/commander'
import { UserModule } from './user/user.module'
import { DatabaseModule } from './database/database.module'
import { GreetCommand } from './greet.command'

@CliModule({
commands: [GreetCommand],
imports: [UserModule, DatabaseModule],
})
export class AppModule {}

Module Lifecycle

Modules can implement lifecycle hooks from @navios/core:

import { CliModule } from '@navios/commander'
import type { NaviosModule } from '@navios/core'

@CliModule({
commands: [GreetCommand],
})
export class AppModule implements NaviosModule {
async onModuleInit() {
console.log('AppModule initialized')
// Module initialization logic
}
}

Nested Module Imports

Modules can import other modules that also import modules:

// User Module
@CliModule({
commands: [CreateUserCommand, DeleteUserCommand],
})
export class UserModule {}

// Admin Module (imports User Module)
@CliModule({
commands: [AdminCommand],
imports: [UserModule], // Commands from UserModule are available
})
export class AdminModule {}

// Root Module (imports Admin Module)
@CliModule({
imports: [AdminModule], // All commands from AdminModule and UserModule are available
})
export class AppModule {}

Module Best Practices

Organize commands by feature or domain:

// ✅ Good: Commands grouped by feature
@CliModule({
commands: [
CreateUserCommand,
DeleteUserCommand,
ListUsersCommand,
],
})
export class UserModule {}

// ❌ Avoid: Unrelated commands together
@CliModule({
commands: [
CreateUserCommand,
MigrateCommand,
SendEmailCommand,
],
})
export class MixedModule {}

2. Use Feature Modules

Create modules for each feature area:

// ✅ Good: Feature-based modules
@CliModule({ commands: [...] })
export class UserModule {}

@CliModule({ commands: [...] })
export class DatabaseModule {}

@CliModule({ commands: [...] })
export class EmailModule {}

@CliModule({
imports: [UserModule, DatabaseModule, EmailModule],
})
export class AppModule {}

3. Keep Modules Focused

Each module should have a clear purpose:

// ✅ Good: Focused module
@CliModule({
commands: [
CreateUserCommand,
UpdateUserCommand,
DeleteUserCommand,
],
})
export class UserModule {}

// ❌ Avoid: Module doing too much
@CliModule({
commands: [
CreateUserCommand,
MigrateCommand,
SendEmailCommand,
ProcessPaymentCommand,
],
})
export class EverythingModule {}

4. Use Imports for Composition

Compose larger applications from smaller modules:

// ✅ Good: Composed from feature modules
@CliModule({
imports: [
UserModule,
DatabaseModule,
EmailModule,
],
})
export class AppModule {}

// ❌ Avoid: Everything in one module
@CliModule({
commands: [
// 50+ commands...
],
})
export class MonolithicModule {}

5. Organize by Directory Structure

Match your module structure to your directory structure:

src/
commands/
user/
create-user.command.ts
delete-user.command.ts
user.module.ts
database/
migrate.command.ts
seed.command.ts
database.module.ts
app.module.ts

Module Examples

Simple CLI Tool

@CliModule({
commands: [
BuildCommand,
TestCommand,
LintCommand,
],
})
export class CliModule {}

Multi-Feature Application

// Feature modules
@CliModule({ commands: [...] })
export class UserModule {}

@CliModule({ commands: [...] })
export class ProductModule {}

@CliModule({ commands: [...] })
export class OrderModule {}

// Root module
@CliModule({
imports: [
UserModule,
ProductModule,
OrderModule,
],
})
export class AppModule {}

Development Tools

@CliModule({ commands: [...] })
export class DatabaseModule {}

@CliModule({ commands: [...] })
export class MigrationModule {}

@CliModule({ commands: [...] })
export class SeedModule {}

@CliModule({
imports: [
DatabaseModule,
MigrationModule,
SeedModule,
],
})
export class DevToolsModule {}

Module Registration

Modules are automatically registered when you create the application:

import { CommanderFactory } from '@navios/commander'
import { AppModule } from './app.module'

async function bootstrap() {
const app = await CommanderFactory.create(AppModule)
await app.init() // Modules are loaded here
await app.run(process.argv)
await app.close()
}

bootstrap()

Module Discovery

All commands from imported modules are automatically available:

// UserModule has: user:create, user:delete
@CliModule({
commands: [CreateUserCommand, DeleteUserCommand],
})
export class UserModule {}

// AppModule imports UserModule
@CliModule({
imports: [UserModule],
})
export class AppModule {}

// All commands are available:
// - user:create
// - user:delete

Next Steps