Module System
Understanding Hypermodern Modules
The Hypermodern module system enables you to create reusable, self-contained components that encapsulate related functionality, schemas, and services. Modules promote code reuse, maintainability, and team collaboration by allowing you to package and distribute functionality as discrete units.
What Makes a Module
A Hypermodern module consists of:
- Module Manifest: Metadata describing the module's identity, dependencies, and exports
- Schema Definitions: Models, endpoints, and enums specific to the module
- Service Providers: Business logic and endpoint implementations
- Migrations: Database schema changes and data transformations
- Configuration Schema: Configurable parameters and their validation rules
- Documentation: Usage examples and API documentation
Module Structure
my_module/
├── module.json # Module manifest
├── schemas/
│ ├── models.json # Module-specific models
│ ├── endpoints.json # Module endpoints
│ └── enums.json # Module enumerations
├── lib/
│ ├── services/ # Business logic
│ ├── providers/ # Service providers
│ ├── middleware/ # Module-specific middleware
│ └── migrations/ # Database migrations
├── config/
│ └── schema.json # Configuration schema
├── tests/
│ └── module_test.dart # Module tests
├── examples/
│ └── usage_example.dart # Usage examples
└── README.md # Module documentation
Creating Custom Modules
Let's create a comprehensive authentication module to demonstrate module development.
Module Manifest
{
"name": "hypermodern_auth",
"version": "1.2.0",
"description": "Complete authentication and authorization module",
"author": "Your Name <your.email@example.com>",
"license": "MIT",
"homepage": "https://github.com/yourorg/hypermodern_auth",
"hypermodern_version": ">=1.0.0 <2.0.0",
"dependencies": {
"hypermodern_crypto": "^1.0.0",
"hypermodern_email": "^0.5.0"
},
"exports": {
"models": [
"User",
"AuthToken",
"Permission",
"Role"
],
"endpoints": [
"login",
"logout",
"register",
"refresh_token",
"reset_password",
"verify_email"
],
"enums": [
"AuthProvider",
"TokenType",
"PermissionLevel"
],
"services": [
"AuthService",
"TokenService",
"PermissionService"
]
},
"configuration": {
"jwt_secret": {
"type": "string",
"required": true,
"description": "Secret key for JWT token signing"
},
"token_expiry": {
"type": "duration",
"default": "24h",
"description": "Default token expiration time"
},
"password_min_length": {
"type": "int32",
"default": 8,
"minimum": 6,
"description": "Minimum password length"
},
"enable_email_verification": {
"type": "bool",
"default": true,
"description": "Require email verification for new accounts"
},
"oauth_providers": {
"type": "map<string, object>",
"default": {},
"description": "OAuth provider configurations"
}
},
"permissions": [
"auth.login",
"auth.register",
"auth.admin",
"users.read",
"users.write",
"users.delete"
],
"database_tables": [
"users",
"auth_tokens",
"roles",
"permissions",
"user_roles"
]
}
Module Schema Definitions
schemas/models.json:
{
"models": {
"user": {
"id": "int64",
"email": "string",
"username": "string",
"password_hash": "string",
"email_verified": "bool",
"email_verification_token": "string?",
"password_reset_token": "string?",
"password_reset_expires": "datetime?",
"last_login": "datetime?",
"created_at": "datetime",
"updated_at": "datetime",
"roles": ["@role"]
},
"auth_token": {
"id": "int64",
"user_id": "int64",
"token_hash": "string",
"type": "@token_type",
"expires_at": "datetime",
"created_at": "datetime",
"last_used": "datetime?",
"metadata": "map<string, any>"
},
"role": {
"id": "int64",
"name": "string",
"description": "string?",
"permissions": ["@permission"],
"created_at": "datetime"
},
"permission": {
"id": "int64",
"name": "string",
"description": "string?",
"resource": "string",
"action": "string"
}
}
}
schemas/endpoints.json:
{
"endpoints": {
"login": {
"method": "POST",
"path": "/auth/login",
"description": "Authenticate user and return access token",
"request": {
"email": "string",
"password": "string",
"remember_me": "bool?"
},
"response": {
"user": "@user",
"access_token": "string",
"refresh_token": "string",
"expires_in": "int32"
},
"errors": ["invalid_credentials", "account_locked", "email_not_verified"],
"transports": ["http", "websocket", "tcp"]
},
"register": {
"method": "POST",
"path": "/auth/register",
"description": "Create new user account",
"request": {
"email": "string",
"username": "string",
"password": "string",
"confirm_password": "string"
},
"response": {
"user": "@user",
"verification_required": "bool"
},
"errors": ["email_exists", "username_exists", "weak_password"],
"transports": ["http", "websocket", "tcp"]
},
"refresh_token": {
"method": "POST",
"path": "/auth/refresh",
"description": "Refresh access token using refresh token",
"request": {
"refresh_token": "string"
},
"response": {
"access_token": "string",
"refresh_token": "string",
"expires_in": "int32"
},
"errors": ["invalid_token", "token_expired"],
"transports": ["http", "websocket", "tcp"]
}
}
}
Module Service Implementation
lib/services/auth_service.dart:
import 'package:hypermodern/hypermodern.dart';
import 'package:hypermodern_server/hypermodern_server.dart';
import '../generated/models.dart';
class AuthService {
final Database db;
final ModuleConfiguration config;
final TokenService tokenService;
final PasswordService passwordService;
AuthService({
required this.db,
required this.config,
required this.tokenService,
required this.passwordService,
});
Future<LoginResponse> login(LoginRequest request) async {
// Find user by email
final user = await _findUserByEmail(request.email);
if (user == null) {
throw InvalidCredentialsException('Invalid email or password');
}
// Verify password
final isValidPassword = await passwordService.verify(
request.password,
user.passwordHash,
);
if (!isValidPassword) {
await _recordFailedLogin(user.id);
throw InvalidCredentialsException('Invalid email or password');
}
// Check if email is verified (if required)
if (config.getBool('enable_email_verification') && !user.emailVerified) {
throw EmailNotVerifiedException('Please verify your email address');
}
// Check account status
if (await _isAccountLocked(user.id)) {
throw AccountLockedException('Account is temporarily locked');
}
// Generate tokens
final tokenExpiry = config.getDuration('token_expiry');
final accessToken = await tokenService.generateAccessToken(user, tokenExpiry);
final refreshToken = await tokenService.generateRefreshToken(user);
// Update last login
await _updateLastLogin(user.id);
// Record successful login
await _recordSuccessfulLogin(user.id);
return LoginResponse(
user: user,
accessToken: accessToken,
refreshToken: refreshToken,
expiresIn: tokenExpiry.inSeconds,
);
}
Future<RegisterResponse> register(RegisterRequest request) async {
// Validate passwords match
if (request.password != request.confirmPassword) {
throw ValidationException('Passwords do not match');
}
// Validate password strength
await passwordService.validateStrength(
request.password,
minLength: config.getInt('password_min_length'),
);
// Check if email already exists
if (await _emailExists(request.email)) {
throw EmailExistsException('Email address is already registered');
}
// Check if username already exists
if (await _usernameExists(request.username)) {
throw UsernameExistsException('Username is already taken');
}
// Hash password
final passwordHash = await passwordService.hash(request.password);
// Create user
final user = await db.transaction((tx) async {
final newUser = await _createUser(tx, User(
email: request.email,
username: request.username,
passwordHash: passwordHash,
emailVerified: !config.getBool('enable_email_verification'),
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
));
// Assign default role
await _assignDefaultRole(tx, newUser.id);
return newUser;
});
// Send verification email if required
bool verificationRequired = false;
if (config.getBool('enable_email_verification')) {
await _sendVerificationEmail(user);
verificationRequired = true;
}
return RegisterResponse(
user: user,
verificationRequired: verificationRequired,
);
}
Future<RefreshTokenResponse> refreshToken(RefreshTokenRequest request) async {
// Validate refresh token
final tokenData = await tokenService.validateRefreshToken(request.refreshToken);
if (tokenData == null) {
throw InvalidTokenException('Invalid refresh token');
}
// Get user
final user = await _findUserById(tokenData.userId);
if (user == null) {
throw InvalidTokenException('User not found');
}
// Generate new tokens
final tokenExpiry = config.getDuration('token_expiry');
final newAccessToken = await tokenService.generateAccessToken(user, tokenExpiry);
final newRefreshToken = await tokenService.generateRefreshToken(user);
// Revoke old refresh token
await tokenService.revokeToken(request.refreshToken);
return RefreshTokenResponse(
accessToken: newAccessToken,
refreshToken: newRefreshToken,
expiresIn: tokenExpiry.inSeconds,
);
}
// Private helper methods
Future<User?> _findUserByEmail(String email) async {
final result = await db.query(
'SELECT * FROM users WHERE email = ? AND deleted_at IS NULL',
[email],
);
return result.isEmpty ? null : User.fromJson(result.first);
}
Future<bool> _emailExists(String email) async {
final result = await db.query(
'SELECT 1 FROM users WHERE email = ? AND deleted_at IS NULL',
[email],
);
return result.isNotEmpty;
}
Future<void> _recordSuccessfulLogin(int userId) async {
await db.query(
'INSERT INTO login_attempts (user_id, success, ip_address, created_at) VALUES (?, ?, ?, ?)',
[userId, true, 'unknown', DateTime.now()],
);
}
}
Module Provider
lib/providers/auth_provider.dart:
import 'package:hypermodern_server/hypermodern_server.dart';
import '../services/auth_service.dart';
import '../services/token_service.dart';
import '../services/password_service.dart';
class AuthModuleProvider extends ModuleProvider {
@override
String get name => 'hypermodern_auth';
@override
Future<void> register(ModuleContainer container) async {
// Register services
container.singleton<PasswordService>((c) => PasswordService());
container.singleton<TokenService>((c) => TokenService(
database: c.get<Database>(),
jwtSecret: c.config.getString('jwt_secret'),
));
container.singleton<AuthService>((c) => AuthService(
db: c.get<Database>(),
config: c.config,
tokenService: c.get<TokenService>(),
passwordService: c.get<PasswordService>(),
));
}
@override
Future<void> boot(ModuleContainer container) async {
final server = container.get<HypermodernServer>();
final authService = container.get<AuthService>();
// Register endpoints
server.registerEndpoint<LoginRequest, LoginResponse>(
'login',
(request) => authService.login(request),
);
server.registerEndpoint<RegisterRequest, RegisterResponse>(
'register',
(request) => authService.register(request),
);
server.registerEndpoint<RefreshTokenRequest, RefreshTokenResponse>(
'refresh_token',
(request) => authService.refreshToken(request),
);
// Register middleware
server.middleware.add(AuthenticationMiddleware(
tokenService: container.get<TokenService>(),
));
}
@override
List<Migration> get migrations => [
CreateUsersTableMigration(),
CreateAuthTokensTableMigration(),
CreateRolesTableMigration(),
CreatePermissionsTableMigration(),
];
}
Module Manifests and Configuration
Module manifests define the module's identity, dependencies, and configuration schema.
Advanced Manifest Features
{
"name": "hypermodern_payments",
"version": "2.1.0",
"description": "Payment processing module with multiple provider support",
"tags": ["payments", "stripe", "paypal", "billing"],
"keywords": ["payment", "billing", "subscription", "invoice"],
"compatibility": {
"hypermodern": ">=1.2.0 <3.0.0",
"dart": ">=3.0.0 <4.0.0"
},
"dependencies": {
"hypermodern_auth": "^1.0.0",
"hypermodern_notifications": "^0.8.0"
},
"optional_dependencies": {
"hypermodern_analytics": "^1.0.0"
},
"peer_dependencies": {
"hypermodern_users": "^2.0.0"
},
"configuration": {
"default_currency": {
"type": "string",
"default": "USD",
"enum": ["USD", "EUR", "GBP", "JPY"],
"description": "Default currency for transactions"
},
"payment_providers": {
"type": "object",
"required": true,
"properties": {
"stripe": {
"type": "object",
"properties": {
"secret_key": {"type": "string", "required": true},
"webhook_secret": {"type": "string", "required": true},
"enabled": {"type": "bool", "default": true}
}
},
"paypal": {
"type": "object",
"properties": {
"client_id": {"type": "string", "required": true},
"client_secret": {"type": "string", "required": true},
"sandbox": {"type": "bool", "default": false}
}
}
}
},
"webhook_endpoints": {
"type": "array",
"items": {"type": "string"},
"default": [],
"description": "External webhook URLs to notify on payment events"
},
"retry_policy": {
"type": "object",
"properties": {
"max_attempts": {"type": "int32", "default": 3, "minimum": 1},
"backoff_multiplier": {"type": "float64", "default": 2.0},
"initial_delay": {"type": "duration", "default": "1s"}
}
}
},
"events": [
"payment.created",
"payment.succeeded",
"payment.failed",
"subscription.created",
"subscription.cancelled",
"invoice.generated"
],
"hooks": {
"pre_install": ["scripts/pre_install.dart"],
"post_install": ["scripts/setup_webhooks.dart"],
"pre_uninstall": ["scripts/cleanup.dart"]
},
"assets": [
"templates/invoice.html",
"templates/receipt.html",
"static/payment_icons/"
]
}
Configuration Validation
class ModuleConfiguration {
final Map<String, dynamic> _config;
final Map<String, ConfigurationField> _schema;
ModuleConfiguration(this._config, this._schema) {
_validate();
}
void _validate() {
for (final entry in _schema.entries) {
final key = entry.key;
final field = entry.value;
final value = _config[key];
// Check required fields
if (field.required && value == null) {
throw ConfigurationException('Required field missing: $key');
}
// Skip validation for null optional fields
if (value == null) continue;
// Type validation
if (!_isValidType(value, field.type)) {
throw ConfigurationException('Invalid type for $key: expected ${field.type}');
}
// Range validation
if (field.minimum != null && value < field.minimum!) {
throw ConfigurationException('Value for $key below minimum: ${field.minimum}');
}
if (field.maximum != null && value > field.maximum!) {
throw ConfigurationException('Value for $key above maximum: ${field.maximum}');
}
// Enum validation
if (field.enumValues != null && !field.enumValues!.contains(value)) {
throw ConfigurationException('Invalid value for $key: must be one of ${field.enumValues}');
}
// Pattern validation
if (field.pattern != null && value is String) {
if (!RegExp(field.pattern!).hasMatch(value)) {
throw ConfigurationException('Value for $key does not match pattern: ${field.pattern}');
}
}
}
}
T get<T>(String key) {
final value = _config[key];
if (value == null) {
final field = _schema[key];
if (field?.defaultValue != null) {
return field!.defaultValue as T;
}
throw ConfigurationException('Configuration key not found: $key');
}
return value as T;
}
String getString(String key) => get<String>(key);
int getInt(String key) => get<int>(key);
bool getBool(String key) => get<bool>(key);
double getDouble(String key) => get<double>(key);
List<T> getList<T>(String key) => (get<List>(key)).cast<T>();
Map<String, T> getMap<T>(String key) => (get<Map>(key)).cast<String, T>();
Duration getDuration(String key) {
final value = getString(key);
return _parseDuration(value);
}
Duration _parseDuration(String value) {
final regex = RegExp(r'^(\d+)(ms|s|m|h|d)$');
final match = regex.firstMatch(value);
if (match == null) {
throw ConfigurationException('Invalid duration format: $value');
}
final amount = int.parse(match.group(1)!);
final unit = match.group(2)!;
switch (unit) {
case 'ms': return Duration(milliseconds: amount);
case 's': return Duration(seconds: amount);
case 'm': return Duration(minutes: amount);
case 'h': return Duration(hours: amount);
case 'd': return Duration(days: amount);
default: throw ConfigurationException('Unknown duration unit: $unit');
}
}
}
Sharing and Distributing Modules
Module Registry
class ModuleRegistry {
final String registryUrl;
final HttpClient httpClient;
ModuleRegistry({
this.registryUrl = 'https://registry.hypermodern.dev',
required this.httpClient,
});
Future<List<ModuleInfo>> search({
String? query,
List<String>? tags,
String? author,
int limit = 20,
int offset = 0,
}) async {
final params = <String, String>{
'limit': limit.toString(),
'offset': offset.toString(),
};
if (query != null) params['q'] = query;
if (tags != null) params['tags'] = tags.join(',');
if (author != null) params['author'] = author;
final uri = Uri.parse('$registryUrl/modules/search').replace(
queryParameters: params,
);
final response = await httpClient.get(uri);
if (response.statusCode != 200) {
throw RegistryException('Search failed: ${response.statusCode}');
}
final data = jsonDecode(response.body) as Map<String, dynamic>;
final modules = (data['modules'] as List)
.map((m) => ModuleInfo.fromJson(m))
.toList();
return modules;
}
Future<ModuleInfo> getModuleInfo(String name, {String? version}) async {
final path = version != null ? '/modules/$name/$version' : '/modules/$name';
final uri = Uri.parse('$registryUrl$path');
final response = await httpClient.get(uri);
if (response.statusCode == 404) {
throw ModuleNotFoundException('Module not found: $name');
} else if (response.statusCode != 200) {
throw RegistryException('Failed to get module info: ${response.statusCode}');
}
final data = jsonDecode(response.body) as Map<String, dynamic>;
return ModuleInfo.fromJson(data);
}
Future<void> publishModule(String modulePath, String apiKey) async {
final manifest = await _loadManifest(modulePath);
final packageData = await _createPackage(modulePath);
final request = http.MultipartRequest('POST', Uri.parse('$registryUrl/modules'));
request.headers['Authorization'] = 'Bearer $apiKey';
request.fields['manifest'] = jsonEncode(manifest.toJson());
request.files.add(http.MultipartFile.fromBytes(
'package',
packageData,
filename: '${manifest.name}-${manifest.version}.tar.gz',
));
final response = await request.send();
if (response.statusCode != 201) {
throw RegistryException('Failed to publish module: ${response.statusCode}');
}
}
Future<String> downloadModule(String name, String version, String targetPath) async {
final uri = Uri.parse('$registryUrl/modules/$name/$version/download');
final response = await httpClient.get(uri);
if (response.statusCode != 200) {
throw RegistryException('Failed to download module: ${response.statusCode}');
}
final packagePath = path.join(targetPath, '$name-$version.tar.gz');
await File(packagePath).writeAsBytes(response.bodyBytes);
// Extract package
await _extractPackage(packagePath, targetPath);
return path.join(targetPath, name);
}
}
Module Installation
class ModuleInstaller {
final ModuleRegistry registry;
final String modulesPath;
ModuleInstaller({
required this.registry,
required this.modulesPath,
});
Future<void> install(String moduleSpec) async {
final (name, version) = _parseModuleSpec(moduleSpec);
print('Installing $name${version != null ? '@$version' : ''}...');
// Get module info
final moduleInfo = await registry.getModuleInfo(name, version: version);
// Check compatibility
await _checkCompatibility(moduleInfo);
// Resolve dependencies
final dependencies = await _resolveDependencies(moduleInfo);
// Install dependencies first
for (final dep in dependencies) {
if (!await _isModuleInstalled(dep.name, dep.version)) {
await install('${dep.name}@${dep.version}');
}
}
// Download and install module
final modulePath = await registry.downloadModule(
moduleInfo.name,
moduleInfo.version,
modulesPath,
);
// Run installation hooks
await _runInstallationHooks(modulePath, moduleInfo);
// Update module registry
await _updateInstalledModules(moduleInfo);
print('✅ Successfully installed ${moduleInfo.name}@${moduleInfo.version}');
}
Future<void> uninstall(String moduleName) async {
print('Uninstalling $moduleName...');
// Check if module is installed
if (!await _isModuleInstalled(moduleName)) {
throw ModuleException('Module not installed: $moduleName');
}
// Check for dependent modules
final dependents = await _findDependentModules(moduleName);
if (dependents.isNotEmpty) {
throw ModuleException(
'Cannot uninstall $moduleName: required by ${dependents.join(', ')}',
);
}
final modulePath = path.join(modulesPath, moduleName);
final manifest = await _loadManifest(modulePath);
// Run uninstallation hooks
await _runUninstallationHooks(modulePath, manifest);
// Remove module files
await Directory(modulePath).delete(recursive: true);
// Update module registry
await _removeFromInstalledModules(moduleName);
print('✅ Successfully uninstalled $moduleName');
}
Future<List<ModuleDependency>> _resolveDependencies(ModuleInfo moduleInfo) async {
final resolved = <ModuleDependency>[];
final visited = <String>{};
await _resolveDependenciesRecursive(
moduleInfo.dependencies,
resolved,
visited,
);
return resolved;
}
Future<void> _resolveDependenciesRecursive(
Map<String, String> dependencies,
List<ModuleDependency> resolved,
Set<String> visited,
) async {
for (final entry in dependencies.entries) {
final name = entry.key;
final versionConstraint = entry.value;
if (visited.contains(name)) {
continue; // Avoid circular dependencies
}
visited.add(name);
// Find compatible version
final version = await _findCompatibleVersion(name, versionConstraint);
final moduleInfo = await registry.getModuleInfo(name, version: version);
// Recursively resolve dependencies
await _resolveDependenciesRecursive(
moduleInfo.dependencies,
resolved,
visited,
);
resolved.add(ModuleDependency(name: name, version: version));
}
}
}
Module Lifecycle and Hooks
Lifecycle Events
abstract class ModuleLifecycle {
Future<void> onInstall(ModuleContext context);
Future<void> onUninstall(ModuleContext context);
Future<void> onEnable(ModuleContext context);
Future<void> onDisable(ModuleContext context);
Future<void> onUpdate(ModuleContext context, String fromVersion);
Future<void> onConfigurationChange(ModuleContext context, Map<String, dynamic> oldConfig);
}
class PaymentModuleLifecycle implements ModuleLifecycle {
@override
Future<void> onInstall(ModuleContext context) async {
// Set up database tables
await context.database.runMigrations();
// Create default configuration
await _createDefaultConfiguration(context);
// Set up webhook endpoints
await _setupWebhooks(context);
print('Payment module installed successfully');
}
@override
Future<void> onUninstall(ModuleContext context) async {
// Clean up webhooks
await _cleanupWebhooks(context);
// Archive payment data (don't delete for compliance)
await _archivePaymentData(context);
print('Payment module uninstalled');
}
@override
Future<void> onEnable(ModuleContext context) async {
// Start background services
await _startPaymentProcessor(context);
await _startWebhookListener(context);
print('Payment module enabled');
}
@override
Future<void> onDisable(ModuleContext context) async {
// Stop background services
await _stopPaymentProcessor(context);
await _stopWebhookListener(context);
print('Payment module disabled');
}
@override
Future<void> onUpdate(ModuleContext context, String fromVersion) async {
print('Updating payment module from $fromVersion to ${context.module.version}');
// Run version-specific migrations
if (_shouldMigrateFrom(fromVersion, '1.0.0')) {
await _migrateFrom1_0_0(context);
}
if (_shouldMigrateFrom(fromVersion, '2.0.0')) {
await _migrateFrom2_0_0(context);
}
// Update webhook configurations
await _updateWebhooks(context);
print('Payment module updated successfully');
}
@override
Future<void> onConfigurationChange(ModuleContext context, Map<String, dynamic> oldConfig) async {
final newConfig = context.configuration;
// Check if payment providers changed
if (oldConfig['payment_providers'] != newConfig.get('payment_providers')) {
await _reconfigurePaymentProviders(context);
}
// Check if webhook endpoints changed
if (oldConfig['webhook_endpoints'] != newConfig.get('webhook_endpoints')) {
await _updateWebhookEndpoints(context);
}
print('Payment module configuration updated');
}
}
Custom Installation Scripts
// scripts/setup_webhooks.dart
import 'dart:io';
import 'package:hypermodern_server/hypermodern_server.dart';
Future<void> main(List<String> args) async {
final context = ModuleContext.fromArgs(args);
final config = context.configuration;
print('Setting up payment webhooks...');
// Set up Stripe webhooks
if (config.has('payment_providers.stripe')) {
await setupStripeWebhooks(context);
}
// Set up PayPal webhooks
if (config.has('payment_providers.paypal')) {
await setupPayPalWebhooks(context);
}
print('✅ Webhooks configured successfully');
}
Future<void> setupStripeWebhooks(ModuleContext context) async {
final stripeConfig = context.configuration.getMap('payment_providers.stripe');
final secretKey = stripeConfig['secret_key'] as String;
final stripe = StripeClient(secretKey);
// Create webhook endpoint
final webhook = await stripe.webhookEndpoints.create({
'url': '${context.serverUrl}/webhooks/stripe',
'enabled_events': [
'payment_intent.succeeded',
'payment_intent.payment_failed',
'invoice.payment_succeeded',
'customer.subscription.created',
'customer.subscription.deleted',
],
});
// Store webhook secret in configuration
await context.updateConfiguration({
'payment_providers.stripe.webhook_secret': webhook.secret,
});
print('Stripe webhook created: ${webhook.id}');
}
What's Next
You now understand how to create, configure, and distribute Hypermodern modules. In the next chapter, we'll explore ORM and data management features, including database integration, migrations, and relationship modeling that work seamlessly with the module system.