Overview
The Module class is the foundation of the Nest Dart modular architecture. It provides a structured way to organize your application’s dependencies, control service visibility, and manage initialization/cleanup lifecycle hooks.
Key Features:
- Modular dependency injection with import/export system
- Service encapsulation and access control
- Async initialization support
- Lifecycle hooks for setup and teardown
- Prevents circular dependencies
Class Definition
Properties
imports
List<Module> get imports => []
List of modules that this module depends on. Services from imported modules are only accessible if they are exported by those modules.
Modules that this module imports. Override this getter to specify dependencies.
Example:
class UserModule extends Module {
@override
List<Module> get imports => [DatabaseModule(), AuthModule()];
}
exports
List<Type> get exports => []
List of provider types that this module exports to other modules. Only exported services can be accessed by modules that import this module.
Service types to make available to importing modules.
Only services listed in exports can be accessed by other modules. Keep this list minimal to maintain proper encapsulation.
Example:
class UserModule extends Module {
@override
List<Type> get exports => [UserService, UserRepository];
}
Methods
providers
Future<void> providers(Locator locator) async {}
Configure providers/services for dependency injection. This method should register all services this module provides. Can be async to support services that require async initialization.
The scoped dependency injection container for registering services.
Returns: Future<void>
The locator parameter is scoped to this module and enforces access restrictions. You can only access services from imported modules that are exported.
Example:
class UserModule extends Module {
@override
Future<void> providers(Locator locator) async {
// Register a singleton
locator.registerSingleton<UserService>(
UserService(locator.get<UserRepository>())
);
// Register a lazy singleton that depends on async initialization
final prefs = await SharedPreferences.getInstance();
locator.registerSingleton<SharedPreferences>(prefs);
// Register a factory
locator.registerFactory<UserFactory>(
() => UserFactory(locator.get<UserService>())
);
}
}
onModuleInit
Future<void> onModuleInit(Locator locator, ModuleContext context) async {}
Called after module registration and dependency resolution. Use this for initialization logic that requires access to fully registered services.
Scoped locator for accessing registered services within this module.
The module context containing export/import metadata.
Returns: Future<void>
Common Use Cases:
- Database connections and migrations
- Service warm-up and configuration
- Data seeding
- Health checks
- Running startup tasks
Example:
class DatabaseModule extends Module {
@override
Future<void> onModuleInit(Locator locator, ModuleContext context) async {
final db = locator.get<Database>();
await db.connect();
await db.runMigrations();
print('Database initialized and migrations completed');
}
}
onModuleDestroy
Future<void> onModuleDestroy(Locator locator, ModuleContext context) async {}
Called when the module is being destroyed/reset. Use this for cleanup logic to properly release resources.
Scoped locator for accessing registered services within this module.
The module context containing export/import metadata.
Returns: Future<void>
Common Use Cases:
- Closing database connections
- Saving state
- Releasing resources
- Cleanup operations
- Flushing caches
Example:
class DatabaseModule extends Module {
@override
Future<void> onModuleDestroy(Locator locator, ModuleContext context) async {
final db = locator.get<Database>();
await db.close();
print('Database connection closed');
}
}
register
Future<void> register(GetIt getIt, ModuleContext context) async
Public method to register this module with the dependency injection container. This is typically called by ApplicationContainer and should not be called directly.
The GetIt instance to register services with.
The module context for tracking dependencies.
Returns: Future<void>
This method is internal to the framework. Use ApplicationContainer.registerModule() instead of calling this directly.
Complete Example
import 'package:nest_core/core.dart';
// Define services
class UserRepository {
Future<User> findById(String id) async {
// Implementation
}
}
class UserService {
final UserRepository repository;
UserService(this.repository);
Future<User> getUser(String id) => repository.findById(id);
}
// Create a module
class UserModule extends Module {
@override
List<Module> get imports => [DatabaseModule()];
@override
List<Type> get exports => [UserService];
@override
Future<void> providers(Locator locator) async {
// Register internal repository (not exported)
locator.registerLazySingleton<UserRepository>(
() => UserRepository(),
);
// Register service (exported)
locator.registerLazySingleton<UserService>(
() => UserService(locator.get<UserRepository>()),
);
}
@override
Future<void> onModuleInit(Locator locator, ModuleContext context) async {
final userService = locator.get<UserService>();
print('UserModule initialized');
}
@override
Future<void> onModuleDestroy(Locator locator, ModuleContext context) async {
print('UserModule cleanup complete');
}
}
// Use the module
void main() async {
final container = ApplicationContainer();
await container.registerModule(UserModule());
// Access exported service
final userService = container.get<UserService>();
final user = await userService.getUser('123');
}
Exceptions
ServiceNotExportedException
Thrown when trying to access a service that is not exported by the module that provides it.
class ServiceNotExportedException implements Exception {
final Type serviceType;
final Type fromModule;
final Type toModule;
}
Example:
// DatabaseModule doesn't export DatabaseConnection
class AppModule extends Module {
@override
List<Module> get imports => [DatabaseModule()];
@override
Future<void> providers(Locator locator) async {
// This will throw ServiceNotExportedException if
// DatabaseConnection is not in DatabaseModule.exports
final db = locator.get<DatabaseConnection>();
}
}
Best Practices
- Keep exports minimal: Only export services that other modules need to access
- Use lazy singletons: Prefer
registerLazySingleton over registerSingleton for better performance
- Organize by feature: Create modules around feature boundaries (e.g., UserModule, AuthModule)
- Initialize in onModuleInit: Place async initialization logic in
onModuleInit rather than providers
- Clean up resources: Always implement
onModuleDestroy if your module allocates resources
- Avoid circular dependencies: Structure your module imports to prevent circular references