JetLeaf
🍃 JetLeaf is a comprehensive, enterprise-grade Dart backend framework that brings Jetleaf-like dependency injection, configuration management, and application lifecycle to Dart server-side development.
JetLeaf provides a complete foundation for building scalable, maintainable backend applications with powerful features like annotation-driven configuration, automatic component scanning, profile-based environments, and event-driven architecture.
- Homepage: https://jetleaf.hapnium.com
- Repository: https://github.com/jetleaf/jetleaf
- License: See
LICENSE
Contents
- Features
- Install
- Quick Start
- Core Concepts
- Usage
- Configuration Files
- CLI Tools
- API Reference
- Examples
- Changelog
- Contributing
- Compatibility
Features
Core Framework
- 🚀 Application Bootstrap –
JetApplicationfor rapid application startup with@JetLeafApplication - 💉 Dependency Injection – Full IoC container with constructor, field, and setter injection
- 📦 Component Scanning – Automatic discovery of
@Component,@Service,@Repository,@Controller - ⚙️ Configuration Management – YAML, JSON, Properties, and Dart-based configuration
- 🔄 Lifecycle Management –
@PostConstruct,@PreDestroy, and context lifecycle hooks - 🎯 Profile Management – Environment-specific configuration with
@Profile - 🔀 Conditional Processing –
@Conditional,@ConditionalOnProperty,@ConditionalOnPod - 📡 Event System – Application-wide event publishing and listening
- 🌍 Internationalization – Multi-locale message resolution
- 🎨 Banner Display – Customizable startup banners
Advanced Features
- Auto-Configuration –
@EnableAutoConfigurationfor framework and library auto-setup - Property Binding –
@Valuefor property injection with placeholders and expressions - Type Conversion – Automatic type conversion for properties and dependencies
- Circular Reference Handling – Smart resolution of circular dependencies
- Lazy Initialization –
@Lazyfor deferred pod creation - Primary Pods –
@Primaryfor disambiguation when multiple candidates exist - Ordered Components –
@Orderfor component ordering - Exit Code Management – Graceful shutdown with exit code generation
- Exception Handling – Comprehensive exception reporting and handling
Install
Add to your pubspec.yaml:
dependencies:
jetleaf:
hosted: https://onepub.dev/api/fahnhnofly/
version: ^1.0.0
Minimum SDK: Dart ^3.9.0
Import:
import 'package:jetleaf/jetleaf.dart';
Quick Start
1. Create Your Application
import 'package:jetleaf/jetleaf.dart';
void main(List<String> args) {
JetApplication.run(MyApplication, args);
}
@JetLeafApplication()
class MyApplication {}
2. Define Services
@Service()
class UserService {
final UserRepository repository;
UserService(this.repository);
Future<User> findById(String id) async {
return await repository.findById(id);
}
}
@Repository()
class UserRepository {
Future<User> findById(String id) async {
// Database access logic
return User(id: id, name: 'John Doe');
}
}
3. Create Configuration
@Configuration()
class DatabaseConfig {
@Pod()
DatabaseConnection databaseConnection() {
return DatabaseConnection(
host: 'localhost',
port: 5432,
database: 'myapp',
);
}
}
4. Run Your Application
dart run lib/main.dart
Core Concepts
Application Bootstrap
JetLeaf applications start with the JetApplication class, which handles:
- Runtime Detection: AOT vs JIT compilation detection
- Bootstrap Context: Early component registration
- Environment Setup: Property sources and profile activation
- Context Creation: Application context building and configuration
- Lifecycle Management: Startup, refresh, and shutdown orchestration
void main(List<String> args) async {
final app = JetApplication(MyApplication);
// Custom configuration
app.setBannerMode(BannerMode.CONSOLE);
app.setAdditionalProfiles({'production'});
app.setLazyInitialization(true);
// Add initializers
app.addInitializer((context) {
// Custom initialization
});
// Launch
final context = await app.create(args, null);
}
Dependency Injection
JetLeaf provides comprehensive dependency injection with multiple strategies:
Constructor Injection (recommended):
@Service()
class OrderService {
final UserService userService;
final PaymentService paymentService;
OrderService(this.userService, this.paymentService);
}
Field Injection:
@Service()
class OrderService {
@Autowired()
late UserService userService;
@Autowired()
late PaymentService paymentService;
}
Auto-Injection:
@Service()
@RequiredAll()
class OrderService {
late UserService userService; // Auto-injected
late PaymentService paymentService; // Auto-injected
}
Configuration Management
JetLeaf supports multiple configuration formats:
YAML Configuration (application.yaml):
server:
port: 8080
host: localhost
database:
url: postgresql://localhost:5432/myapp
maxConnections: 20
logging:
level: INFO
Properties Configuration (application.properties):
server.port=8080
server.host=localhost
database.url=postgresql://localhost:5432/myapp
database.maxConnections=20
Dart Configuration:
@Configuration()
class AppConfig {
@Pod()
ServerConfig serverConfig() {
return ServerConfig(port: 8080, host: 'localhost');
}
}
Component Scanning
JetLeaf automatically discovers and registers components:
Stereotype Annotations:
@Component– Generic component@Service– Business logic layer@Repository– Data access layer@Controller– Presentation/routing layer
@Service()
class EmailService {
Future<void> sendEmail(String to, String subject, String body) async {
// Email sending logic
}
}
@Repository()
class UserRepository {
Future<User> save(User user) async {
// Database persistence
}
}
@Controller()
class UserController {
final UserService userService;
UserController(this.userService);
}
Lifecycle Management
Control component lifecycle with annotations:
@Service()
class DatabaseService {
late DatabaseConnection connection;
@PostConstruct()
Future<void> initialize() async {
connection = await DatabaseConnection.connect();
print('Database connected');
}
@PreDestroy()
Future<void> cleanup() async {
await connection.close();
print('Database connection closed');
}
}
Profile Management
Configure environment-specific behavior:
@Configuration()
@Profile('development')
class DevConfig {
@Pod()
Logger logger() => Logger.debug();
}
@Configuration()
@Profile('production')
class ProdConfig {
@Pod()
Logger logger() => Logger.production();
}
Activate profiles via command line:
dart run lib/main.dart --jetleaf.profiles.active=production
Usage
Creating an Application
import 'package:jetleaf/jetleaf.dart';
@JetLeafApplication()
class MyApplication {
static void main(List<String> args) {
JetApplication.run(MyApplication, args);
}
}
With custom configuration:
void main(List<String> args) async {
final app = JetApplication(MyApplication);
// Banner configuration
app.setBannerMode(BannerMode.CONSOLE);
// Profile activation
app.setAdditionalProfiles({'production', 'cloud'});
// Performance tuning
app.setLazyInitialization(true);
app.setAllowCircularReferences(false);
// Custom initializers
app.addInitializer((context) async {
// Pre-refresh initialization
});
// Event listeners
app.addListener(MyApplicationListener());
// Launch application
final context = await app.create(args, null);
}
Configuration Classes
@Configuration()
class DatabaseConfig {
@Value('#{database.url}')
late String databaseUrl;
@Value('#{database.maxConnections:20}')
late int maxConnections;
@Pod()
DatabaseConnection primaryDatabase() {
return DatabaseConnection(
url: databaseUrl,
maxConnections: maxConnections,
);
}
@Pod('readOnlyDatabase')
@Scope('prototype')
DatabaseConnection readOnlyDatabase() {
return DatabaseConnection(
url: databaseUrl,
readOnly: true,
);
}
}
Services and Components
@Service()
class UserService {
final UserRepository repository;
final EmailService emailService;
final Logger logger;
UserService(this.repository, this.emailService, this.logger);
Future<User> registerUser(CreateUserRequest request) async {
logger.info('Registering user: ${request.email}');
final user = await repository.save(User.fromRequest(request));
await emailService.sendWelcomeEmail(user.email);
return user;
}
}
@Repository()
class UserRepository {
final DatabaseConnection db;
UserRepository(this.db);
Future<User> save(User user) async {
// Database persistence logic
return user;
}
Future<User?> findByEmail(String email) async {
// Database query logic
return null;
}
}
Property Injection
@Component()
class ApiClient {
@Value('#{api.baseUrl}')
late String baseUrl;
@Value('#{api.timeout:30}')
late int timeout;
@Value('#{api.apiKey}')
late String apiKey;
Future<Response> get(String endpoint) async {
final url = '$baseUrl$endpoint';
// HTTP request logic
}
}
Conditional Configuration
// Conditional on property
@ConditionalOnProperty(
prefix: 'cache',
names: ['enabled'],
havingValue: 'true',
)
@Configuration()
class CacheConfig {
@Pod()
CacheManager cacheManager() => RedisCacheManager();
}
// Conditional on pod existence
@ConditionalOnPod(DatabaseConnection)
@Service()
class DatabaseMigrationService {
final DatabaseConnection db;
DatabaseMigrationService(this.db);
@PostConstruct()
Future<void> runMigrations() async {
// Migration logic
}
}
// Conditional on missing pod
@ConditionalOnMissingPod(CacheManager)
@Configuration()
class DefaultCacheConfig {
@Pod()
CacheManager cacheManager() => InMemoryCacheManager();
}
Application Runners
Execute logic after application startup:
@Component()
class DataInitializer implements ApplicationRunner {
final UserRepository userRepository;
DataInitializer(this.userRepository);
@override
Future<void> run(ApplicationArguments args) async {
print('Initializing data...');
await userRepository.seedDefaultUsers();
}
}
@Component()
class CommandLineProcessor implements CommandLineRunner {
@override
Future<void> run(List<String> args) async {
print('Processing command line arguments: $args');
}
}
Event Handling
// Define custom event
class UserRegisteredEvent extends ApplicationEvent {
final User user;
UserRegisteredEvent(Object source, this.user) : super(source);
}
// Publish event
@Service()
class UserService {
final ApplicationContext context;
UserService(this.context);
Future<User> registerUser(CreateUserRequest request) async {
final user = await userRepository.save(request);
// Publish event
await context.publishEvent(UserRegisteredEvent(this, user));
return user;
}
}
// Listen to event
@Component()
class UserEventListener implements ApplicationEventListener<UserRegisteredEvent> {
final EmailService emailService;
UserEventListener(this.emailService);
@override
Future<void> onApplicationEvent(UserRegisteredEvent event) async {
await emailService.sendWelcomeEmail(event.user.email);
}
}
Configuration Files
JetLeaf supports multiple configuration file formats in the resources/ directory:
application.yaml
jetleaf:
application:
name: MyApplication
version: 1.0.0
profiles:
active: development
server:
port: 8080
host: 0.0.0.0
database:
url: postgresql://localhost:5432/myapp
username: admin
password: secret
maxConnections: 20
logging:
level: INFO
pattern: "%d{yyyy-MM-dd HH:mm:ss} [%level] %logger - %msg"
application.properties
jetleaf.application.name=MyApplication
jetleaf.application.version=1.0.0
jetleaf.profiles.active=development
server.port=8080
server.host=0.0.0.0
database.url=postgresql://localhost:5432/myapp
database.username=admin
database.password=secret
Profile-Specific Configuration
application-dev.yaml– Development profileapplication-prod.yaml– Production profileapplication-test.yaml– Test profile
CLI Tools
JetLeaf provides CLI tools for development and deployment:
Development
# Run in development mode with hot reload
jl dev
# Run with specific profile
jl dev --profile=development
Build and Deploy
# Generate bootstrap file
jl generate
# Build production executable
jl build
# Generate and build
jl serve
# Run production build
dart run build/main.dill
API Reference
Main Exports (lib/jetleaf.dart)
- Bootstrap:
BootstrapContext - Application:
JetApplication,JetLeafApplication - Lang: Re-exports from
jetleaf_lang(Class, Annotation, etc.) - Environment: Re-exports from
jetleaf_env(Environment, PropertyResolver, etc.) - Core: Re-exports from
jetleaf_core(ApplicationContext, annotations, etc.) - Convert: Re-exports from
jetleaf_convert(ConversionService, Converters, etc.) - Pod: Re-exports from
jetleaf_pod(PodFactory, PodDefinition, etc.)
Key Classes
JetApplication: Main application bootstrap class@JetLeafApplication: Application entry point annotation@EnableAutoConfiguration: Enable auto-configurationApplicationContext: Central application containerConfigurableApplicationContext: Configurable context interface
Annotations
- Configuration:
@Configuration,@AutoConfiguration,@Pod - Stereotypes:
@Component,@Service,@Repository,@Controller - Dependency Injection:
@Autowired,@Value,@Qualifier,@RequiredAll - Lifecycle:
@PostConstruct,@PreDestroy,@Lazy - Conditional:
@Conditional,@ConditionalOnProperty,@ConditionalOnPod - Others:
@Primary,@Scope,@Order,@Profile
Examples
REST API Application
@JetLeafApplication()
class ApiApplication {
static void main(List<String> args) {
JetApplication.run(ApiApplication, args);
}
}
@Controller()
class UserController {
final UserService userService;
UserController(this.userService);
Future<Response> getUser(String id) async {
final user = await userService.findById(id);
return Response.ok(user);
}
}
Microservice with Database
@JetLeafApplication()
@EnableAutoConfiguration()
class MicroserviceApplication {
static void main(List<String> args) {
JetApplication.run(MicroserviceApplication, args);
}
}
@Configuration()
class DatabaseConfig {
@Pod()
DatabaseConnection database(
@Value('#{database.url}') String url,
@Value('#{database.maxConnections}') int maxConnections,
) {
return DatabaseConnection(url: url, maxConnections: maxConnections);
}
}
Scheduled Tasks
@Service()
class ScheduledTaskService {
@Scheduled(cron: '0 0 * * * *') // Every hour
Future<void> hourlyTask() async {
print('Running hourly task');
}
@Scheduled(fixedDelay: Duration(minutes: 5))
Future<void> periodicTask() async {
print('Running every 5 minutes');
}
}
Changelog
See CHANGELOG.md.
Contributing
Issues and PRs are welcome at the GitHub repository.
- Fork and create a feature branch.
- Add tests for new functionality.
- Run
dart testand ensure lints pass. - Open a PR with a concise description and examples.
Compatibility
- Dart SDK:
>=3.9.0 <4.0.0 - Depends on:
jetleaf_lang,jetleaf_logging,jetleaf_convert,jetleaf_core,jetleaf_utils,jetleaf_env,jetleaf_pod
Built with 🍃 by the JetLeaf team.