Module Basics
Modules are a powerful way to organize and structure your dependency injection setup in NexusDI. They allow you to group related services, providers, and configuration into logical units that can be easily imported, reused, and tested. Much like the modular habitat systems on Mars, each module has specialized functions but can be connected to create something greater than the sum of its parts.
What are Modules?​
A module is a class decorated with @Module() that defines a collection of:
- Services - Classes that provide functionality
- Providers - Dependencies that can be injected
- Imports - Other modules to include
- Exports - Services/providers to make available to other modules
Why Use Modules?​
1. Organization & Structure​
Modules help you organize your application into logical, cohesive units:
// Instead of one giant Nexus container with everything mixed together
const container = new Nexus();
container.set(USER_SERVICE, { useClass: UserService });
container.set(EMAIL_SERVICE, { useClass: EmailService });
container.set(DATABASE, { useClass: Database });
container.set(LOGGER, { useClass: Logger });
// ... 50 more services
// You can organize into focused modules
@Module({
providers: [UserService, UserRepository],
})
class UserModule {}
@Module({
providers: [
EmailService,
NotificationService,
{ token: EMAIL_CONFIG, useValue: emailConfig },
],
})
class NotificationModule {}
2. Reusability​
Modules can be reused across different applications or parts of your application:
// Reusable authentication module
@Module({
providers: [
AuthService,
JwtService,
PasswordService,
{ token: AUTH_CONFIG, useValue: authConfig },
],
})
class AuthModule {}
// Use in different applications
const app1 = new Nexus();
app1.set(AuthModule);
app1.set(UserModule);
const app2 = new Nexus();
app2.set(AuthModule);
app2.set(OrderModule);
3. Testing & Mocking​
Modules make it easier to test specific parts of your application:
// Test with mocked dependencies
@Module({
providers: [UserService],
providers: [
{ token: DATABASE, useValue: mockDatabase },
{ token: LOGGER, useValue: mockLogger },
],
})
class TestUserModule {}
const testContainer = new Nexus();
testContainer.set(TestUserModule);
4. Configuration Management​
Modules can encapsulate configuration and environment-specific settings:
@Module({
providers: [DatabaseService],
providers: [
{
token: DATABASE_CONFIG,
useFactory: () => ({
host: process.env.DB_HOST,
port: process.env.DB_PORT,
database: process.env.DB_NAME,
}),
},
],
})
class DatabaseModule {}
Creating Modules​
Basic Module Structure​
import { Module, Service, Token, Inject } from '@nexusdi/core';
// Define tokens
export const USER_SERVICE = new Token<IUserService>('USER_SERVICE');
export const DATABASE = new Token<IDatabase>('DATABASE');
// Define services
@Service(USER_SERVICE)
class UserService implements IUserService {
constructor(@Inject(DATABASE) private db: IDatabase) {}
async getUser(id: string) {
return this.db.query(`SELECT * FROM users WHERE id = ?`, [id]);
}
}
// Create the module
@Module({
providers: [UserService, { token: DATABASE, useClass: PostgresDatabase }],
})
export class UserModule {}
Module with Imports​
Modules can import other modules to compose functionality:
@Module({
providers: [
AuthService,
{ token: JWT_SECRET, useValue: process.env.JWT_SECRET },
],
})
class AuthModule {}
@Module({
providers: [UserService, { token: DATABASE, useClass: PostgresDatabase }],
imports: [AuthModule], // Import the auth module
})
class UserModule {}
Module with Exports​
Export services to make them available to other modules:
@Module({
providers: [
DatabaseService,
ConnectionPool,
{ token: DATABASE_CONFIG, useValue: dbConfig },
],
exports: [DATABASE_SERVICE], // Export for other modules to use
})
class DatabaseModule {}
Module Registration​
Register Modules​
import { Nexus } from '@nexusdi/core';
import { UserModule, OrderModule } from './modules';
const container = new Nexus();
// Register individual modules
container.set(UserModule);
container.set(OrderModule);
// Or register multiple at once
container.set(UserModule, OrderModule);
// Async dynamic modules (see Dynamic Modules doc for details)
const config = await SomeModule.configAsync(options);
container.set(config); // Always await the result of configAsync() before passing it to set. See Dynamic Modules for details.
Module Dependencies​
Modules can depend on each other through imports:
@Module({
providers: [DatabaseService, { token: DATABASE_CONFIG, useValue: dbConfig }],
})
class DatabaseModule {}
@Module({
providers: [UserService],
imports: [DatabaseModule], // Depends on DatabaseModule
})
class UserModule {}
@Module({
providers: [OrderService],
imports: [DatabaseModule, UserModule], // Depends on both
})
class OrderModule {}
Module Configuration​
Provider Registration​
NexusDI supports two formats for registering providers in modules:
Simplified Format (Recommended)​
When a service is decorated with @Service(token), you can simply include the service class in the providers array:
@Module({
providers: [
LoggerService, // Automatically uses the token from @Service decorator
UserService, // Automatically uses the token from @Service decorator
],
})
class AppModule {}
Full Provider Format​
For more complex scenarios, you can use the full provider object format:
@Module({
providers: [
{ token: LOGGER_SERVICE_TOKEN, useClass: LoggerService },
{ token: USER_SERVICE_TOKEN, useClass: UserService },
{ token: CONFIG_TOKEN, useValue: { apiUrl: 'https://api.example.com' } },
{ token: FACTORY_TOKEN, useFactory: () => new SomeService() },
],
})
class AppModule {}
Configuration Injection in Services​
Services within the module can inject the configuration:
@Service(DATABASE_SERVICE)
class DatabaseService {
constructor(@Inject(DATABASE_CONFIG) private config: DatabaseConfig) {}
async connect() {
console.log(
`Connecting to ${this.config.host}:${this.config.port}/${this.config.database}`
);
// Connection logic
}
}
Summary​
Modules provide a powerful way to organize your dependency injection setup:
- Organization: Group related services and providers into logical units
- Reusability: Share modules across different applications
- Testing: Easily mock and test specific parts of your application
- Configuration: Encapsulate environment-specific settings
- Composability: Import and combine modules as needed
For advanced module patterns and dynamic configuration, see Module Patterns and Dynamic Modules.
For advanced topics such as dynamic modules, lifetimes, multi-injection, and more, see the Advanced section.
Next Steps​
- Module Patterns - Advanced patterns and best practices
- Dynamic Modules - Runtime configuration and validation
- Testing - How to test modules and services