Skip to main content

Server Development

Creating Hypermodern Servers

Building a Hypermodern server means creating an application that can simultaneously handle HTTP, WebSocket, and TCP connections while sharing the same business logic across all protocols.

Basic Server Setup

import 'package:hypermodern_server/hypermodern_server.dart';
import 'generated/models.dart';
import 'generated/server.dart';

void main() async {
  final server = HypermodernServer();
  
  // Configure server
  server.configure(ServerConfig(
    // Protocol ports
    httpPort: 8080,
    wsPort: 8082,
    tcpPort: 8081,
    
    // Performance settings
    maxConnections: 1000,
    requestTimeout: Duration(seconds: 30),
    keepAliveTimeout: Duration(seconds: 60),
    
    // Security
    corsEnabled: true,
    corsOrigins: ['http://localhost:3000', 'https://myapp.com'],
    rateLimitEnabled: true,
    rateLimitRequests: 100,
    rateLimitWindow: Duration(minutes: 1),
  ));
  
  // Register endpoints
  _registerEndpoints(server);
  
  // Start all protocol servers
  await server.listen();
  
  print('🚀 Hypermodern server running on:');
  print('   HTTP:      http://localhost:8080');
  print('   WebSocket: ws://localhost:8082');
  print('   TCP:       localhost:8081');
}

Server Configuration Options

class ServerConfig {
  // Protocol configuration
  final int httpPort;
  final int wsPort;
  final int tcpPort;
  final String bindAddress;
  
  // Performance tuning
  final int maxConnections;
  final int maxRequestSize;
  final Duration requestTimeout;
  final Duration keepAliveTimeout;
  final int workerThreads;
  
  // Security settings
  final bool corsEnabled;
  final List<String> corsOrigins;
  final bool rateLimitEnabled;
  final int rateLimitRequests;
  final Duration rateLimitWindow;
  final bool validateRequests;
  
  // Logging and monitoring
  final LogLevel logLevel;
  final bool metricsEnabled;
  final String? metricsEndpoint;
  
  // SSL/TLS (for production)
  final String? sslCertPath;
  final String? sslKeyPath;
  final bool requireSsl;
  
  const ServerConfig({
    this.httpPort = 8080,
    this.wsPort = 8082,
    this.tcpPort = 8081,
    this.bindAddress = '0.0.0.0',
    this.maxConnections = 1000,
    this.maxRequestSize = 1024 * 1024, // 1MB
    this.requestTimeout = const Duration(seconds: 30),
    this.keepAliveTimeout = const Duration(seconds: 60),
    this.workerThreads = 4,
    this.corsEnabled = false,
    this.corsOrigins = const [],
    this.rateLimitEnabled = false,
    this.rateLimitRequests = 100,
    this.rateLimitWindow = const Duration(minutes: 1),
    this.validateRequests = true,
    this.logLevel = LogLevel.info,
    this.metricsEnabled = false,
    this.metricsEndpoint,
    this.sslCertPath,
    this.sslKeyPath,
    this.requireSsl = false,
  });
}

Implementing Endpoint Handlers

Endpoint handlers contain your business logic and are automatically available across all protocols.

Basic Endpoint Implementation

void _registerEndpoints(HypermodernServer server) {
  // Simple request-response endpoint
  server.registerEndpoint<GetUserRequest, User>(
    'get_user',
    (request) async {
      final user = await userService.getUserById(request.id);
      if (user == null) {
        throw NotFoundException('User not found', details: {
          'user_id': request.id.toString(),
        });
      }
      return user;
    },
  );
  
  // Endpoint with validation
  server.registerEndpoint<CreateUserRequest, User>(
    'create_user',
    (request) async {
      // Validate request
      if (request.username.length < 3) {
        throw ValidationException('Username too short', fieldErrors: {
          'username': 'Must be at least 3 characters',
        });
      }
      
      if (!_isValidEmail(request.email)) {
        throw ValidationException('Invalid email format', fieldErrors: {
          'email': 'Must be a valid email address',
        });
      }
      
      // Check for existing user
      final existingUser = await userService.getUserByEmail(request.email);
      if (existingUser != null) {
        throw ConflictException('User already exists', details: {
          'email': request.email,
        });
      }
      
      // Create user
      final user = await userService.createUser(
        username: request.username,
        email: request.email,
        passwordHash: _hashPassword(request.password),
      );
      
      return user;
    },
  );
  
  // Endpoint with complex response
  server.registerEndpoint<SearchUsersRequest, SearchUsersResponse>(
    'search_users',
    (request) async {
      final results = await userService.searchUsers(
        query: request.query,
        filters: request.filters,
        pagination: request.pagination,
        sort: request.sort,
      );
      
      return SearchUsersResponse(
        users: results.users,
        totalCount: results.totalCount,
        page: request.pagination.page,
        hasMore: results.hasMore,
        facets: results.facets,
      );
    },
  );
}

Async and Database Operations

class UserService {
  final Database db;
  
  UserService(this.db);
  
  Future<User?> getUserById(int id) async {
    final result = await db.query(
      'SELECT * FROM users WHERE id = ?',
      [id],
    );
    
    if (result.isEmpty) return null;
    
    return User.fromJson(result.first);
  }
  
  Future<User> createUser({
    required String username,
    required String email,
    required String passwordHash,
  }) async {
    final now = DateTime.now();
    
    final result = await db.query(
      '''
      INSERT INTO users (username, email, password_hash, created_at, updated_at)
      VALUES (?, ?, ?, ?, ?)
      RETURNING *
      ''',
      [username, email, passwordHash, now, now],
    );
    
    return User.fromJson(result.first);
  }
  
  Future<SearchResult> searchUsers({
    String? query,
    UserFilters? filters,
    required Pagination pagination,
    required Sort sort,
  }) async {
    final queryBuilder = QueryBuilder('users');
    
    // Add search conditions
    if (query != null && query.isNotEmpty) {
      queryBuilder.where('username ILIKE ? OR email ILIKE ?', 
                        ['%$query%', '%$query%']);
    }
    
    // Add filters
    if (filters?.status != null) {
      queryBuilder.where('status = ?', [filters!.status!.toString()]);
    }
    
    if (filters?.createdAfter != null) {
      queryBuilder.where('created_at > ?', [filters!.createdAfter]);
    }
    
    // Add sorting
    queryBuilder.orderBy('${sort.field} ${sort.direction}');
    
    // Add pagination
    queryBuilder.limit(pagination.limit);
    queryBuilder.offset(pagination.page * pagination.limit);
    
    // Execute queries
    final countQuery = queryBuilder.buildCountQuery();
    final dataQuery = queryBuilder.buildSelectQuery();
    
    final [countResult, dataResult] = await Future.wait([
      db.query(countQuery.sql, countQuery.parameters),
      db.query(dataQuery.sql, dataQuery.parameters),
    ]);
    
    final totalCount = countResult.first['count'] as int;
    final users = dataResult.map((row) => User.fromJson(row)).toList();
    
    return SearchResult(
      users: users,
      totalCount: totalCount,
      hasMore: (pagination.page + 1) * pagination.limit < totalCount,
    );
  }
}

Error Handling in Endpoints

server.registerEndpoint<UpdateUserRequest, User>(
  'update_user',
  (request) async {
    try {
      // Validate permissions
      final currentUser = await getCurrentUser(request.context);
      if (currentUser.id != request.id && !currentUser.isAdmin) {
        throw ForbiddenException('Cannot update other users');
      }
      
      // Get existing user
      final existingUser = await userService.getUserById(request.id);
      if (existingUser == null) {
        throw NotFoundException('User not found');
      }
      
      // Validate updates
      if (request.email != null && !_isValidEmail(request.email!)) {
        throw ValidationException('Invalid email format');
      }
      
      // Check for email conflicts
      if (request.email != null && request.email != existingUser.email) {
        final emailExists = await userService.getUserByEmail(request.email!);
        if (emailExists != null) {
          throw ConflictException('Email already in use');
        }
      }
      
      // Perform update
      final updatedUser = await userService.updateUser(
        id: request.id,
        username: request.username,
        email: request.email,
        profile: request.profile,
      );
      
      // Log the update
      await auditService.logUserUpdate(
        userId: currentUser.id,
        targetUserId: request.id,
        changes: request.getChanges(existingUser),
      );
      
      return updatedUser;
      
    } on DatabaseException catch (e) {
      // Handle database-specific errors
      if (e.isUniqueConstraintViolation) {
        throw ConflictException('Duplicate value detected');
      } else if (e.isForeignKeyViolation) {
        throw ValidationException('Referenced entity does not exist');
      } else {
        throw ServerException('Database operation failed');
      }
    } on ValidationException {
      rethrow; // Re-throw validation errors as-is
    } catch (e) {
      // Log unexpected errors
      logger.error('Unexpected error in update_user', error: e);
      throw ServerException('Internal server error');
    }
  },
);

Middleware and Request Processing

Middleware provides a powerful way to add cross-cutting concerns like authentication, logging, and validation.

Built-in Middleware

void configureMiddleware(HypermodernServer server) {
  // Request logging
  server.middleware.add(LoggingMiddleware(
    logRequests: true,
    logResponses: true,
    logHeaders: false, // Don't log sensitive headers
    logBodies: false,  // Don't log request/response bodies
  ));
  
  // CORS handling
  server.middleware.add(CorsMiddleware(
    allowedOrigins: ['http://localhost:3000', 'https://myapp.com'],
    allowedMethods: ['GET', 'POST', 'PUT', 'DELETE'],
    allowedHeaders: ['Content-Type', 'Authorization'],
    allowCredentials: true,
  ));
  
  // Rate limiting
  server.middleware.add(RateLimitMiddleware(
    requests: 100,
    window: Duration(minutes: 1),
    keyGenerator: (request) => request.clientIp,
  ));
  
  // Request validation
  server.middleware.add(ValidationMiddleware(
    validateRequests: true,
    validateResponses: false, // Disable in production
  ));
  
  // Authentication (applied to specific endpoints)
  server.middleware.add(AuthenticationMiddleware(
    jwtSecret: 'your-secret-key',
    requiredForEndpoints: [
      'create_user',
      'update_user',
      'delete_user',
    ],
  ));
}

Custom Middleware

class TimingMiddleware implements Middleware {
  @override
  Future<dynamic> handle(
    dynamic request,
    Future<dynamic> Function(dynamic) next,
  ) async {
    final stopwatch = Stopwatch()..start();
    
    try {
      final response = await next(request);
      
      final duration = stopwatch.elapsedMilliseconds;
      logger.info('Request completed in ${duration}ms', extra: {
        'endpoint': request.endpoint,
        'duration_ms': duration,
      });
      
      return response;
    } catch (e) {
      final duration = stopwatch.elapsedMilliseconds;
      logger.error('Request failed in ${duration}ms', error: e, extra: {
        'endpoint': request.endpoint,
        'duration_ms': duration,
      });
      rethrow;
    }
  }
}

class CachingMiddleware implements Middleware {
  final Map<String, CacheEntry> _cache = {};
  final Duration defaultTtl;
  
  CachingMiddleware({this.defaultTtl = const Duration(minutes: 5)});
  
  @override
  Future<dynamic> handle(
    dynamic request,
    Future<dynamic> Function(dynamic) next,
  ) async {
    // Only cache GET requests
    if (request.method != 'GET') {
      return await next(request);
    }
    
    final cacheKey = _generateCacheKey(request);
    final entry = _cache[cacheKey];
    
    // Return cached response if valid
    if (entry != null && !entry.isExpired) {
      logger.debug('Cache hit for $cacheKey');
      return entry.data;
    }
    
    // Execute request and cache result
    final response = await next(request);
    
    _cache[cacheKey] = CacheEntry(
      data: response,
      expiresAt: DateTime.now().add(defaultTtl),
    );
    
    logger.debug('Cached response for $cacheKey');
    return response;
  }
  
  String _generateCacheKey(dynamic request) {
    return '${request.endpoint}:${request.hashCode}';
  }
}

class SecurityHeadersMiddleware implements Middleware {
  @override
  Future<dynamic> handle(
    dynamic request,
    Future<dynamic> Function(dynamic) next,
  ) async {
    final response = await next(request);
    
    // Add security headers for HTTP responses
    if (request.protocol == 'http') {
      response.headers.addAll({
        'X-Content-Type-Options': 'nosniff',
        'X-Frame-Options': 'DENY',
        'X-XSS-Protection': '1; mode=block',
        'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
        'Content-Security-Policy': "default-src 'self'",
      });
    }
    
    return response;
  }
}

Conditional Middleware

Apply middleware only to specific endpoints or conditions:

class ConditionalMiddleware implements Middleware {
  final Middleware middleware;
  final bool Function(dynamic request) condition;
  
  ConditionalMiddleware(this.middleware, this.condition);
  
  @override
  Future<dynamic> handle(
    dynamic request,
    Future<dynamic> Function(dynamic) next,
  ) async {
    if (condition(request)) {
      return await middleware.handle(request, next);
    } else {
      return await next(request);
    }
  }
}

// Usage
server.middleware.add(ConditionalMiddleware(
  AuthenticationMiddleware(jwtSecret: 'secret'),
  (request) => ['create_user', 'update_user'].contains(request.endpoint),
));

server.middleware.add(ConditionalMiddleware(
  CachingMiddleware(defaultTtl: Duration(hours: 1)),
  (request) => request.method == 'GET' && request.endpoint.startsWith('get_'),
));

Streaming Endpoints

Streaming endpoints enable real-time communication and are automatically available over WebSocket and TCP protocols.

Basic Streaming

void registerStreamingEndpoints(HypermodernServer server) {
  // Server-to-client streaming
  server.registerStreamingEndpoint<WatchUsersRequest, UserUpdate>(
    'watch_users',
    (request) async* {
      // Validate request
      if (request.userIds.isEmpty) {
        throw ValidationException('At least one user ID required');
      }
      
      // Create stream controller
      final controller = StreamController<UserUpdate>();
      
      // Set up database change listener
      final subscription = userService.watchUsers(request.userIds).listen(
        (update) => controller.add(update),
        onError: (error) => controller.addError(error),
        onDone: () => controller.close(),
      );
      
      // Clean up when client disconnects
      controller.onCancel = () {
        subscription.cancel();
      };
      
      yield* controller.stream;
    },
  );
  
  // Bidirectional streaming (chat example)
  server.registerBidirectionalStreamingEndpoint<ChatMessage, ChatMessage>(
    'chat_messages',
    (incomingMessages, outgoingMessages) async {
      final roomId = await _extractRoomId(incomingMessages);
      
      // Join chat room
      await chatService.joinRoom(roomId);
      
      // Forward incoming messages to chat service
      final incomingSubscription = incomingMessages.listen(
        (message) async {
          // Validate and process message
          final processedMessage = await chatService.processMessage(message);
          
          // Broadcast to all room members
          await chatService.broadcastMessage(roomId, processedMessage);
        },
        onError: (error) => logger.error('Chat message error', error: error),
      );
      
      // Forward room messages to client
      final outgoingSubscription = chatService.getRoomMessages(roomId).listen(
        (message) => outgoingMessages.add(message),
        onError: (error) => outgoingMessages.addError(error),
      );
      
      // Clean up when connection closes
      outgoingMessages.onCancel = () async {
        await incomingSubscription.cancel();
        await outgoingSubscription.cancel();
        await chatService.leaveRoom(roomId);
      };
    },
  );
}

Advanced Streaming Patterns

// Stream with filtering and transformation
server.registerStreamingEndpoint<WatchOrdersRequest, OrderUpdate>(
  'watch_orders',
  (request) async* {
    final userId = await getCurrentUserId(request.context);
    
    // Create filtered stream
    final orderStream = orderService
        .watchAllOrders()
        .where((order) => order.userId == userId)
        .where((order) => request.statuses.isEmpty || 
                         request.statuses.contains(order.status))
        .map((order) => OrderUpdate.fromOrder(order));
    
    // Add rate limiting to prevent overwhelming clients
    final rateLimitedStream = orderStream.throttle(Duration(milliseconds: 100));
    
    yield* rateLimitedStream;
  },
);

// Stream with backpressure handling
server.registerStreamingEndpoint<WatchMetricsRequest, MetricUpdate>(
  'watch_metrics',
  (request) async* {
    final controller = StreamController<MetricUpdate>();
    
    // High-frequency metrics source
    final metricsSubscription = metricsService.getMetricsStream().listen(
      (metric) {
        // Handle backpressure
        if (controller.hasListener && !controller.isPaused) {
          controller.add(MetricUpdate.fromMetric(metric));
        }
      },
      onError: (error) => controller.addError(error),
    );
    
    // Pause/resume based on client backpressure
    controller.onPause = () => metricsSubscription.pause();
    controller.onResume = () => metricsSubscription.resume();
    controller.onCancel = () => metricsSubscription.cancel();
    
    yield* controller.stream;
  },
);

// Stream with periodic updates
server.registerStreamingEndpoint<WatchSystemStatusRequest, SystemStatus>(
  'watch_system_status',
  (request) async* {
    while (true) {
      try {
        final status = await systemService.getCurrentStatus();
        yield status;
        
        // Wait for next update interval
        await Future.delayed(Duration(seconds: request.intervalSeconds));
      } catch (e) {
        // Log error but continue streaming
        logger.error('Error getting system status', error: e);
        
        // Send error status
        yield SystemStatus(
          healthy: false,
          error: e.toString(),
          timestamp: DateTime.now(),
        );
        
        // Wait before retrying
        await Future.delayed(Duration(seconds: 5));
      }
    }
  },
);

Stream Connection Management

class StreamManager {
  final Map<String, Set<StreamSubscription>> _activeStreams = {};
  final Map<String, int> _connectionCounts = {};
  
  void registerStream(String clientId, String endpoint, StreamSubscription subscription) {
    _activeStreams.putIfAbsent(clientId, () => {}).add(subscription);
    _connectionCounts[endpoint] = (_connectionCounts[endpoint] ?? 0) + 1;
    
    logger.info('Client $clientId subscribed to $endpoint', extra: {
      'endpoint': endpoint,
      'total_connections': _connectionCounts[endpoint],
    });
  }
  
  Future<void> disconnectClient(String clientId) async {
    final streams = _activeStreams.remove(clientId);
    if (streams != null) {
      for (final stream in streams) {
        await stream.cancel();
      }
      
      logger.info('Disconnected client $clientId', extra: {
        'streams_closed': streams.length,
      });
    }
  }
  
  Map<String, int> getConnectionStats() {
    return Map.from(_connectionCounts);
  }
  
  Future<void> shutdown() async {
    for (final streams in _activeStreams.values) {
      for (final stream in streams) {
        await stream.cancel();
      }
    }
    _activeStreams.clear();
    _connectionCounts.clear();
  }
}

Database Integration and ORM

Hypermodern servers typically need robust database integration for persistent storage.

Database Connection Setup

class DatabaseManager {
  late Database _db;
  late ConnectionPool _pool;
  
  Future<void> initialize() async {
    _pool = ConnectionPool(
      host: 'localhost',
      port: 5432,
      database: 'hypermodern_app',
      username: 'app_user',
      password: 'secure_password',
      minConnections: 5,
      maxConnections: 20,
      connectionTimeout: Duration(seconds: 10),
      idleTimeout: Duration(minutes: 10),
    );
    
    _db = Database(_pool);
    
    // Run migrations
    await _runMigrations();
    
    // Set up connection monitoring
    _pool.onConnectionCreated.listen((conn) {
      logger.debug('Database connection created: ${conn.id}');
    });
    
    _pool.onConnectionClosed.listen((conn) {
      logger.debug('Database connection closed: ${conn.id}');
    });
  }
  
  Database get database => _db;
  
  Future<void> _runMigrations() async {
    final migrator = Migrator(_db);
    await migrator.runPendingMigrations();
  }
  
  Future<void> close() async {
    await _pool.close();
  }
}

Repository Pattern

abstract class Repository<T> {
  Future<T?> findById(int id);
  Future<List<T>> findAll();
  Future<T> create(T entity);
  Future<T> update(T entity);
  Future<void> delete(int id);
}

class UserRepository implements Repository<User> {
  final Database db;
  
  UserRepository(this.db);
  
  @override
  Future<User?> findById(int id) async {
    final result = await db.query(
      'SELECT * FROM users WHERE id = ? AND deleted_at IS NULL',
      [id],
    );
    
    return result.isEmpty ? null : User.fromJson(result.first);
  }
  
  @override
  Future<List<User>> findAll() async {
    final result = await db.query(
      'SELECT * FROM users WHERE deleted_at IS NULL ORDER BY created_at DESC',
    );
    
    return result.map((row) => User.fromJson(row)).toList();
  }
  
  Future<User?> findByEmail(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<List<User>> search({
    String? query,
    UserStatus? status,
    DateTime? createdAfter,
    int limit = 50,
    int offset = 0,
  }) async {
    final queryBuilder = QueryBuilder('users')
        .where('deleted_at IS NULL');
    
    if (query != null && query.isNotEmpty) {
      queryBuilder.where(
        '(username ILIKE ? OR email ILIKE ?)',
        ['%$query%', '%$query%'],
      );
    }
    
    if (status != null) {
      queryBuilder.where('status = ?', [status.toString()]);
    }
    
    if (createdAfter != null) {
      queryBuilder.where('created_at > ?', [createdAfter]);
    }
    
    final query_result = queryBuilder
        .orderBy('created_at DESC')
        .limit(limit)
        .offset(offset)
        .build();
    
    final result = await db.query(query_result.sql, query_result.parameters);
    return result.map((row) => User.fromJson(row)).toList();
  }
  
  @override
  Future<User> create(User user) async {
    final result = await db.query(
      '''
      INSERT INTO users (username, email, password_hash, status, created_at, updated_at)
      VALUES (?, ?, ?, ?, ?, ?)
      RETURNING *
      ''',
      [
        user.username,
        user.email,
        user.passwordHash,
        user.status.toString(),
        user.createdAt,
        user.updatedAt,
      ],
    );
    
    return User.fromJson(result.first);
  }
  
  @override
  Future<User> update(User user) async {
    final result = await db.query(
      '''
      UPDATE users 
      SET username = ?, email = ?, status = ?, updated_at = ?
      WHERE id = ? AND deleted_at IS NULL
      RETURNING *
      ''',
      [
        user.username,
        user.email,
        user.status.toString(),
        DateTime.now(),
        user.id,
      ],
    );
    
    if (result.isEmpty) {
      throw NotFoundException('User not found');
    }
    
    return User.fromJson(result.first);
  }
  
  @override
  Future<void> delete(int id) async {
    // Soft delete
    final result = await db.query(
      '''
      UPDATE users 
      SET deleted_at = ?
      WHERE id = ? AND deleted_at IS NULL
      ''',
      [DateTime.now(), id],
    );
    
    if (result.affectedRows == 0) {
      throw NotFoundException('User not found');
    }
  }
}

Transaction Management

class UserService {
  final UserRepository userRepo;
  final ProfileRepository profileRepo;
  final Database db;
  
  UserService(this.userRepo, this.profileRepo, this.db);
  
  Future<User> createUserWithProfile({
    required String username,
    required String email,
    required String password,
    required UserProfile profile,
  }) async {
    return await db.transaction((tx) async {
      // Create user
      final user = await userRepo.create(User(
        username: username,
        email: email,
        passwordHash: _hashPassword(password),
        status: UserStatus.active,
        createdAt: DateTime.now(),
        updatedAt: DateTime.now(),
      ));
      
      // Create profile
      final userProfile = await profileRepo.create(profile.copyWith(
        userId: user.id,
      ));
      
      // Return user with profile
      return user.copyWith(profile: userProfile);
    });
  }
  
  Future<void> transferUserData(int fromUserId, int toUserId) async {
    await db.transaction((tx) async {
      // Verify both users exist
      final fromUser = await userRepo.findById(fromUserId);
      final toUser = await userRepo.findById(toUserId);
      
      if (fromUser == null || toUser == null) {
        throw NotFoundException('One or both users not found');
      }
      
      // Transfer posts
      await tx.query(
        'UPDATE posts SET author_id = ? WHERE author_id = ?',
        [toUserId, fromUserId],
      );
      
      // Transfer comments
      await tx.query(
        'UPDATE comments SET user_id = ? WHERE user_id = ?',
        [toUserId, fromUserId],
      );
      
      // Merge user preferences
      await _mergeUserPreferences(tx, fromUserId, toUserId);
      
      // Deactivate source user
      await tx.query(
        'UPDATE users SET status = ?, updated_at = ? WHERE id = ?',
        [UserStatus.inactive.toString(), DateTime.now(), fromUserId],
      );
    });
  }
}

Background Job Scheduling

Hypermodern servers include a built-in job scheduling system for handling background tasks, delayed execution, and long-running processes without blocking your main application.

Quick Start with Scheduling

import 'package:hypermodern_server/hypermodern_server.dart';

// Define a scheduled job
class SendWelcomeEmailJob extends ScheduledJob {
  @override
  String get identifier => 'send_welcome_email';

  @override
  Future<void> execute(Map<String, dynamic> parameters) async {
    final email = parameters['email'] as String;
    final name = parameters['name'] as String;
    
    await emailService.sendWelcomeEmail(email, name);
  }
}

void main() async {
  final server = HypermodernServer();
  
  // Set up job scheduling
  final jobRegistry = JobRegistry();
  final scheduler = JobScheduler(registry: jobRegistry);
  
  // Register jobs
  jobRegistry.register('send_welcome_email', () => SendWelcomeEmailJob());
  
  // Start the scheduler
  scheduler.start();
  
  // In your endpoint handlers, schedule jobs
  server.registerEndpoint<CreateUserRequest, User>(
    'create_user',
    (request) async {
      final user = await userService.createUser(request);
      
      // Schedule welcome email for 5 minutes later
      await scheduler.scheduleDelayed(
        'send_welcome_email',
        Duration(minutes: 5),
        {
          'email': user.email,
          'name': user.username,
        },
        description: 'Send welcome email to new user',
      );
      
      return user;
    },
  );
  
  await server.listen();
}

Integration with Server Lifecycle

class HypermodernServerWithScheduling extends HypermodernServer {
  late JobScheduler _scheduler;
  late JobRegistry _jobRegistry;
  
  @override
  Future<void> initialize() async {
    await super.initialize();
    
    // Initialize scheduling system
    _jobRegistry = JobRegistry();
    _scheduler = JobScheduler(
      registry: _jobRegistry,
      executionInterval: Duration(seconds: 30),
      maxTasksPerCycle: 50,
    );
    
    // Register your jobs
    _registerJobs();
    
    // Start scheduler
    _scheduler.start();
  }
  
  void _registerJobs() {
    _jobRegistry.register('send_welcome_email', () => SendWelcomeEmailJob());
    _jobRegistry.register('cleanup_old_data', () => CleanupJob());
    _jobRegistry.register('generate_report', () => ReportJob());
  }
  
  JobScheduler get scheduler => _scheduler;
  
  @override
  Future<void> shutdown() async {
    _scheduler.stop();
    await super.shutdown();
  }
}

The scheduling system provides persistent job storage, automatic retries, error handling, and comprehensive monitoring. For detailed information on creating complex jobs, handling failures, and performance optimization, see ChapterPart 11.5:III: Advanced Features: Background Job Scheduling.

What's Next

You now have a solid foundation in Hypermodern server development, including background job scheduling. In the next chapter, we'll explore the multi-protocol communication system in detail, learning how to optimize for each protocol's strengths while maintaining a unified codebase.