The nest_core package is the foundation of the Nest Dart framework. It provides the core dependency injection system, module architecture, and application container that powers all Nest Dart applications.
Overview
nest_core implements a NestJS-inspired module system with the following key components:
ApplicationContainer: Central service registry and lifecycle manager
Module: Base class for organizing related services
Locator: Interface for type-safe service resolution
ModuleContext: Manages module dependencies and exports
Check pub.dev for the latest versions of Nest Dart packages before adding them to your project.
Installation
Code
dependencies: nest_core: ^0.1.1
Core Concepts
ApplicationContainer
The ApplicationContainer is the heart of your application. It manages module registration, service resolution, and application lifecycle.
Code
import 'package:nest_core/nest_core.dart';// Create a containerfinal container = ApplicationContainer();// Register modulesawait container.registerModule(AppModule());// Get servicesfinal userService = container.get<UserService>();// Check if readyif (container.isReady) { print('Container initialized successfully');}
Container Methods
Code
class ApplicationContainer { // Module management Future<void> registerModule(Module module); Future<void> registerModules(List<Module> modules); // Service resolution T get<T extends Object>({String? instanceName}); T getWithParams<T extends Object>(dynamic param1, [dynamic param2]); Future<T> getAsync<T extends Object>({String? instanceName}); // Status checking bool get isReady; bool isRegistered<T extends Object>({String? instanceName}); Set<Type> getAvailableServices(); // Lifecycle Future<void> waitUntilReady({Duration? timeout}); Future<void> allReady({Duration? timeout}); Future<void> reset(); // Access to underlying systems GetIt get getIt; // Use with caution ModuleContext get context; List<Module> get modules;}
Module System
Modules are the building blocks of your application. They group related services and define their dependencies.
Code
class UserModule extends Module { @override List<Module> get imports => [CoreModule(), DatabaseModule()]; @override void providers(Locator locator) { locator.registerSingleton<UserRepository>( UserRepository(locator.get<DatabaseService>()), ); locator.registerSingleton<UserService>( UserService(locator.get<UserRepository>()), ); } @override List<Type> get exports => [UserService];}
Module Lifecycle Hooks
Modules support initialization and cleanup hooks:
Code
class DatabaseModule extends Module { @override void providers(Locator locator) { locator.registerLazySingleton<DatabaseService>(() => DatabaseService()); } @override Future<void> onModuleInit(Locator locator, ModuleContext context) async { final db = locator.get<DatabaseService>(); await db.connect(); await db.runMigrations(); print('Database module initialized'); } @override Future<void> onModuleDestroy(Locator locator, ModuleContext context) async { final db = locator.get<DatabaseService>(); await db.close(); print('Database module destroyed'); } @override List<Type> get exports => [DatabaseService];}
Service Registration
The Locator interface provides several ways to register services:
Singleton Registration
Services are created once and reused throughout the application:
Code
@overridevoid providers(Locator locator) { // Direct instance locator.registerSingleton<ConfigService>(ConfigService()); // With disposal locator.registerSingleton<DatabaseService>( DatabaseService(), dispose: (service) => service.close(), ); // With instance name locator.registerSingleton<ApiClient>( ApiClient('https://api.example.com'), instanceName: 'mainApi', );}
Factory Registration
New instances are created each time the service is requested:
Nest Core enforces strict access control through module exports:
Code
class CoreModule extends Module { @override void providers(Locator locator) { locator.registerSingleton<ConfigService>(ConfigService()); locator.registerSingleton<LoggerService>(LoggerService()); locator.registerSingleton<InternalService>(InternalService()); // Not exported } @override List<Type> get exports => [ConfigService, LoggerService]; // InternalService is private}class UserModule extends Module { @override List<Module> get imports => [CoreModule()]; @override void providers(Locator locator) { // ✅ This works - ConfigService is exported final config = locator.get<ConfigService>(); // ❌ This throws ServiceNotExportedException - InternalService is not exported // final internal = locator.get<InternalService>(); locator.registerSingleton<UserService>(UserService(config)); }}
Advanced Features
Module Context
The ModuleContext provides information about module relationships and service availability:
Code
@overrideFuture<void> onModuleInit(Locator locator, ModuleContext context) async { // Get available services for this module final availableServices = context.getAvailableServices(runtimeType); print('Available services: $availableServices'); // Check if a service can be accessed final canAccessLogger = context.canAccess(runtimeType, LoggerService); print('Can access LoggerService: $canAccessLogger');}
Custom Container
You can provide your own GetIt instance:
Code
final customGetIt = GetIt.asNewInstance();final container = ApplicationContainer(customGetIt);
Error Handling
Nest Core provides specific exceptions for common issues:
Code
try { final service = container.get<PrivateService>();} on ServiceNotExportedException catch (e) { print('Service ${e.serviceType} is not exported by ${e.fromModule}');}
Testing Support
Nest Core makes testing easy with container isolation:
@overridevoid providers(Locator locator) { // Only created when first accessed locator.registerLazySingleton<MLModelService>( () => MLModelService.loadModel(), );}
final container = ApplicationContainer();await container.registerModule(AppModule());// Wait for all services to be readyawait container.allReady(timeout: Duration(seconds: 30));print('Container ready with ${container.modules.length} modules');print('Available services: ${container.getAvailableServices().length}');
class ConfigService { final Map<String, dynamic> _config; ConfigService(this._config); T get<T>(String key) => _config[key] as T; String get databaseUrl => get<String>('database_url'); int get port => get<int>('port'); bool get debugMode => get<bool>('debug_mode');}class ConfigModule extends Module { @override void providers(Locator locator) { final config = _loadConfig(); // Load from environment, files, etc. locator.registerSingleton<ConfigService>(ConfigService(config)); } @override List<Type> get exports => [ConfigService];}
4. Error Handling
Handle initialization errors gracefully:
Code
@overrideFuture<void> onModuleInit(Locator locator, ModuleContext context) async { try { final db = locator.get<DatabaseService>(); await db.connect(); } catch (e) { final logger = locator.get<LoggerService>(); logger.error('Failed to initialize database: $e'); rethrow; }}