Khadem Middleware System

Comprehensive middleware framework with priority-based execution, error handling, and flexible registration patterns.

Overview

Core Features

Priority-based execution
Named middleware
Error handling
Conditional execution
Pipeline management

Execution Flow

1. Global middleware (CORS, logging)
2. Routing middleware
3. Authentication middleware
4. Preprocessing middleware
5. Business logic middleware
6. Terminating middleware

Creating Middleware

dart
import 'package:khadem/khadem_dart.dart';

class CustomMiddleware implements Middleware {
  @override
  MiddlewareHandler get handler => (req, res, next) async {
    // Pre-processing logic
    print('Request to: ${req.path} at ${DateTime.now()}');


    // Call next middleware or route handler
    await next();

    // Post-processing logic
    print('Response sent at ${DateTime.now()}');
  };

  @override
  String get name => 'CustomLogger';

  @override
  MiddlewarePriority get priority => MiddlewarePriority.global;
}

Middleware Contract

  • handler: The main middleware function
  • name: Unique identifier for the middleware
  • priority: Execution order priority level

Priority System

dart
enum MiddlewarePriority {
  global,        // 0 - CORS, logging, security
  routing,       // 1 - Route resolution
  auth,          // 2 - Authentication
  preprocessing, // 3 - Input validation
  business,      // 4 - App-specific logic
  terminating    // 5 - Error handling, cleanup
}

// Usage
class AuthMiddleware implements Middleware {
  @override
  MiddlewarePriority get priority => MiddlewarePriority.auth;
}

Execution Order

Global (0)

CORS, logging, security headers

Routing (1)

Route resolution, URL rewriting

Auth (2)

Authentication, authorization

Preprocessing (3)

Input validation, sanitization

Business (4)

App-specific logic

Terminating (5)

Response formatting, cleanup

Registration Patterns

Global Registration

dart
// Global middleware registration
void setupGlobalMiddleware(Server server) {
  server.useMiddlewares([
    CorsMiddleware(),
    LoggingMiddleware(),
    ExceptionMiddleware(),
    RateLimitMiddleware(),
  ]);
}

// All routes will use these middleware
server.get('/api/users', UserController.index);
server.post('/api/users', UserController.store);

Applies to all routes in the application

Route-specific Registration

dart
// Route-specific middleware
server.get('/api/profile', ProfileController.show,
    middleware: [AuthMiddleware()]);

server.post('/api/admin/users', AdminController.create,
    middleware: [AuthMiddleware(), AdminOnlyMiddleware()]);

server.get('/public/data', PublicController.index,
    middleware: []); // No middleware for public routes

Applies only to specific routes or route groups

Group Registration

dart
// Group middleware
server.group(
  prefix: '/api/v1',
  middleware: [AuthMiddleware(), JsonResponseMiddleware()],
  routes: (router) {
    router.get('/users', UserController.index);
    router.post('/users', UserController.store);
    router.get('/profile', ProfileController.show);
  }
);

// Nested groups inherit parent middleware
server.group(
  prefix: '/api/v1/admin',
  middleware: [AdminOnlyMiddleware()], // Additional middleware
  routes: (router) {
    router.delete('/users/:id', AdminController.delete);
    router.put('/settings', AdminController.updateSettings);
  }
);

Applies to all routes within a group

Conditional Registration

dart
// Conditional middleware
class MaintenanceMiddleware implements Middleware {
  @override
  MiddlewareHandler get handler => (req, res, next) async {
    if (Khadem.config.get('app.maintenance_mode') == true) {
      return res.status(503).json({
        'error': 'Service temporarily unavailable',
        'maintenance': true
      });
    }
    await next();
  };

  @override
  String get name => 'Maintenance';

  @override
  MiddlewarePriority get priority => MiddlewarePriority.global;
}

// Environment-based middleware
class DebugMiddleware implements Middleware {
  @override
  MiddlewareHandler get handler => (req, res, next) async {
    if (Khadem.env.get('APP_DEBUG') == 'true') {
      final start = DateTime.now();
      await next();
      final duration = DateTime.now().difference(start);
      res.header('X-Debug-Time', '${duration.inMilliseconds}ms');
    } else {
      await next();
    }
  };

  @override
  String get name => 'Debug';

  @override
  MiddlewarePriority get priority => MiddlewarePriority.terminating;
}

Executes only when conditions are met

Advanced Patterns

Middleware Composition

dart
// Middleware composition
class ApiMiddlewareStack {
  static List<Middleware> get standard => [
    CorsMiddleware(),
    LoggingMiddleware(),
    AuthMiddleware(),
    ValidationMiddleware(),
    ExceptionMiddleware(),
  ];

  static List<Middleware> get public => [
    CorsMiddleware(),
    LoggingMiddleware(),
    RateLimitMiddleware(),
  ];

  static List<Middleware> get admin => [
    ...standard,
    AdminOnlyMiddleware(),
    AuditMiddleware(),
  ];
}

// Usage
server.group(
  prefix: '/api',
  middleware: ApiMiddlewareStack.standard,
  routes: (router) {
    // Routes with standard middleware
  }
);

server.group(
  prefix: '/admin',
  middleware: ApiMiddlewareStack.admin,
  routes: (router) {
    // Routes with admin middleware
  }
);

Combine multiple middleware into reusable compositions

Dynamic Middleware

dart
// Dynamic middleware with configuration
class RateLimitMiddleware implements Middleware {
  final int maxRequests;
  final Duration window;

  RateLimitMiddleware({
    this.maxRequests = 100,
    this.window = const Duration(minutes: 1)
  });

  @override
  MiddlewareHandler get handler => (req, res, next) async {
    final clientId = req.ip ?? 'anonymous';
    final key = 'rate_limit:$clientId';

    final current = await Khadem.cache.get(key, defaultValue: 0);
    if (current >= maxRequests) {
      return res.status(429).json({
        'error': 'Too many requests',
        'retry_after': window.inSeconds
      });
    }

    await Khadem.cache.put(key, current + 1, ttl: window);
    await next();
  };

  @override
  String get name => 'RateLimit';

  @override
  MiddlewarePriority get priority => MiddlewarePriority.global;
}

// Factory for different rate limits
class RateLimitFactory {
  static Middleware api() => RateLimitMiddleware(maxRequests: 1000);
  static Middleware auth() => RateLimitMiddleware(maxRequests: 5, window: Duration(minutes: 5));
  static Middleware public() => RateLimitMiddleware(maxRequests: 100);
}

Create middleware with runtime configuration

Middleware Factory

dart
// Middleware factory pattern
class ThrottleMiddleware implements Middleware {
  final String name;
  final int maxAttempts;
  final Duration decayMinutes;

  ThrottleMiddleware(this.name, {
    this.maxAttempts = 60,
    this.decayMinutes = const Duration(minutes: 1)
  });

  @override
  MiddlewareHandler get handler => (req, res, next) async {
    final key = 'throttle:$name:${req.ip}';
    final attempts = await Khadem.cache.get(key, defaultValue: 0);

    if (attempts >= maxAttempts) {
      final ttl = await Khadem.cache.ttl(key);
      return res.status(429).json({
        'error': 'Too many attempts',
        'retry_after': ttl
      });
    }

    await Khadem.cache.put(key, attempts + 1, ttl: decayMinutes);
    await next();
  };

  @override
  String get name => this.name;

  @override
  MiddlewarePriority get priority => MiddlewarePriority.global;
}

// Usage
final loginThrottle = ThrottleMiddleware('login', maxAttempts: 5, decayMinutes: Duration(minutes: 15));
final apiThrottle = ThrottleMiddleware('api', maxAttempts: 1000);

Factory pattern for creating parameterized middleware

Error Recovery

dart
// Error recovery middleware
class FallbackMiddleware implements Middleware {
  @override
  MiddlewareHandler get handler => (req, res, next) async {
    try {
      await next();
    } catch (e) {
      // Log the error
      Khadem.logger.error('Request failed', e);

      // Provide fallback response
      if (!res.headersSent) {
        res.status(500).json({
          'error': 'Internal server error',
          'request_id': req.header('x-request-id'),
          'timestamp': DateTime.now().toIso8601String()
        });
      }
    }
  };

  @override
  String get name => 'Fallback';

  @override
  MiddlewarePriority get priority => MiddlewarePriority.global;
}

// Circuit breaker pattern
class CircuitBreakerMiddleware implements Middleware {
  final String serviceName;
  int failureCount = 0;
  DateTime? lastFailureTime;
  bool isOpen = false;

  CircuitBreakerMiddleware(this.serviceName);

  @override
  MiddlewareHandler get handler => (req, res, next) async {
    if (isOpen) {
      // Check if we should attempt to close the circuit
      if (lastFailureTime != null &&
          DateTime.now().difference(lastFailureTime!) > Duration(minutes: 1)) {
        isOpen = false;
        failureCount = 0;
      } else {
        return res.status(503).json({
          'error': 'Service temporarily unavailable',
          'circuit_breaker': 'open'
        });
      }
    }

    try {
      await next();
      // Reset on success
      failureCount = 0;
    } catch (e) {
      failureCount++;
      lastFailureTime = DateTime.now();

      if (failureCount >= 5) {
        isOpen = true;
      }

      throw e; // Re-throw to let other error handlers deal with it
    }
  };

  @override
  String get name => 'CircuitBreaker';

  @override
  MiddlewarePriority get priority => MiddlewarePriority.business;
}

Handle errors and provide fallback responses

Error Handling

dart
// Comprehensive error handling
class ErrorHandlingMiddleware implements Middleware {
  @override
  MiddlewareHandler get handler => (req, res, next) async {
    try {
      await next();
    } catch (e, stackTrace) {
      // Attach error info to request for logging
      req.setAttribute('error', e.toString());
      req.setAttribute('stackTrace', stackTrace.toString());
      req.setAttribute('errorTime', DateTime.now());

      // Handle different error types
      if (e is ValidationException) {
        return res.status(422).json({
          'error': 'Validation failed',
          'fields': e.errors,
          'code': 'VALIDATION_ERROR'
        });
      }

      if (e is AuthenticationException) {
        return res.status(401).json({
          'error': 'Authentication required',
          'code': 'AUTH_REQUIRED'
        });
      }

      if (e is AuthorizationException) {
        return res.status(403).json({
          'error': 'Access denied',
          'code': 'ACCESS_DENIED'
        });
      }

      // Generic error response
      Khadem.logger.error('Unhandled error', e, stackTrace);
      res.status(500).json({
        'error': 'Internal server error',
        'request_id': req.header('x-request-id'),
        'code': 'INTERNAL_ERROR'
      });
    }
  };

  @override
  String get name => 'ErrorHandler';

  @override
  MiddlewarePriority get priority => MiddlewarePriority.terminating;
}

Error Handling Strategies

  • Use terminating middleware for global error handling
  • Attach error information to request for downstream processing
  • Provide fallback responses when middleware fails
  • Log errors with appropriate context
  • Don't swallow errors unless you handle them properly

Complete Example

dart
// Complete middleware implementation example
import 'package:khadem/khadem_dart.dart';

// 1. Custom middleware classes
class RequestLoggerMiddleware implements Middleware {
  @override
  MiddlewareHandler get handler => (req, res, next) async {
    final start = DateTime.now();
    Khadem.logger.info('→ ${req.method} ${req.uri}');

    await next();

    final duration = DateTime.now().difference(start);
    Khadem.logger.info('← ${res.statusCode} in ${duration.inMilliseconds}ms');
  };

  @override
  String get name => 'RequestLogger';

  @override
  MiddlewarePriority get priority => MiddlewarePriority.global;
}

class ApiAuthMiddleware implements Middleware {
  @override
  MiddlewareHandler get handler => (req, res, next) async {
    final token = req.header('authorization')?.replaceFirst('Bearer ', '');

    if (token == null) {
      throw AuthException('No token provided');
    }

    try {
      final payload = await Khadem.auth.verify(token);
      req.setAttribute('user', payload);
      req.setAttribute('userId', payload['id']);
    } catch (e) {
      throw AuthException('Invalid token');
    }

    await next();
  };

  @override
  String get name => 'ApiAuth';

  @override
  MiddlewarePriority get priority => MiddlewarePriority.auth;
}

class ValidationMiddleware implements Middleware {
  @override
  MiddlewareHandler get handler => (req, res, next) async {
    // Add validation context
    req.setAttribute('validated', false);

    await next();

    // Post-validation checks
    if (req.getAttribute('validation_errors') != null) {
      throw ValidationException(req.getAttribute('validation_errors'));
    }
  };

  @override
  String get name => 'Validation';

  @override
  MiddlewarePriority get priority => MiddlewarePriority.preprocessing;
}

class ResponseFormatterMiddleware implements Middleware {
  @override
  MiddlewareHandler get handler => (req, res, next) async {
    await next();

    // Ensure JSON responses have consistent format
    if (req.path.startsWith('/api') && !res.headersSent) {
      final existingData = res.getData();
      if (existingData != null && existingData is! Map) {
        res.json({
          'success': true,
          'data': existingData,
          'timestamp': DateTime.now().toIso8601String()
        });
      }
    }
  };

  @override
  String get name => 'ResponseFormatter';

  @override
  MiddlewarePriority get priority => MiddlewarePriority.terminating;
}

// 2. Server setup with middleware
void setupServer(Server server) {
  // Global middleware
  server.useMiddlewares([
    RequestLoggerMiddleware(),
    CorsMiddleware(),
    ExceptionMiddleware(),
  ]);

  // API routes with authentication
  server.group(
    prefix: '/api/v1',
    middleware: [ApiAuthMiddleware(), ValidationMiddleware()],
    routes: (router) {
      router.get('/users', UserController.index);
      router.post('/users', UserController.store);
      router.get('/users/:id', UserController.show);
      router.put('/users/:id', UserController.update);
      router.delete('/users/:id', UserController.destroy);
    }
  );

  // Public routes
  server.group(
    prefix: '/public',
    routes: (router) {
      router.get('/health', HealthController.check);
      router.get('/version', VersionController.get);
    }
  );

  // Admin routes with additional middleware
  server.group(
    prefix: '/api/v1/admin',
    middleware: [AdminOnlyMiddleware(), AuditMiddleware()],
    routes: (router) {
      router.get('/stats', AdminController.stats);
      router.post('/maintenance', AdminController.maintenance);
    }
  );
}

// 3. Controller example
class UserController {
  static Future index(Request req, Response res) async {
    final users = await User.query()
        .paginate(perPage: 20, page: int.tryParse(req.query['page'] ?? '1') ?? 1);

    res.json({
      'users': users.data,
      'pagination': {
        'page': users.currentPage,
        'total': users.total,
        'per_page': 20,
        'last_page': users.lastPage,
        'has_next': users.currentPage < users.lastPage,
        'has_prev': users.currentPage > 1,
      }
    });
  }

  static Future store(Request req, Response res) async {
    final data = await req.body;

    // Validation is handled by ValidationMiddleware
    final user = await User.create(data);

    res.status(201).json({
      'user': user,
      'message': 'User created successfully'
    });
  }
}

// 4. Custom exceptions
class AuthException implements Exception {
  final String message;
  AuthException(this.message);

  @override
  String toString() => message;
}

class ValidationException implements Exception {
  final Map<String, dynamic> errors;
  ValidationException(this.errors);

  @override
  String toString() => 'Validation failed: $errors';
}

Best Practices

✅ Recommendations

  • Use descriptive names for middleware clarity
  • Choose appropriate priority levels for execution order
  • Always call await next() unless terminating the pipeline
  • Handle errors appropriately in middleware
  • Use dependency injection for testable middleware
  • Keep middleware focused on single responsibilities
  • Document middleware behavior and requirements
  • Use conditional middleware for performance optimization

❌ Avoid

  • Don't perform heavy operations in middleware
  • Don't modify request/response in unexpected ways
  • Don't forget to call next() in non-terminating middleware
  • Don't use middleware for business logic (use controllers)
  • Don't create middleware with too many responsibilities
  • Don't ignore error handling in middleware
  • Don't use global state in middleware
  • Don't make middleware dependent on specific route structures

On this page