Request/Response Pipeline

Middleware System

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

Overview

Understanding the middleware pipeline

Core Features

Priority-based execution order
Named middleware identification
Built-in error handling
Conditional execution logic
Pipeline management

Execution Flow

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

Creating Middleware

Implement custom middleware classes

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

Control middleware execution order

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

0Global

CORS, logging, security headers

1Routing

Route resolution, URL rewriting

2Auth

Authentication, authorization

3Preprocessing

Input validation, sanitization

4Business

App-specific logic

5Terminating

Response formatting, cleanup

Registration Patterns

Different ways to register middleware

Global Registration

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

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

Applies to all routes in the application

Route-specific Registration

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

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

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

Applies only to specific routes or route groups

Group Registration

dart
// Group middleware
router.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
router.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

Powerful middleware composition techniques

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
router.group(
  prefix: '/api',
  middleware: ApiMiddlewareStack.standard,
  routes: (router) {
    // Routes with standard middleware
  }
);

router.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

Robust error handling strategies

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

Full middleware implementation with all features

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.applyMiddlewares([
    RequestLoggerMiddleware(),
    CorsMiddleware(),
    ExceptionMiddleware(),
  ]);

  // API routes with authentication
  router.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
  router.group(
    prefix: '/public',
    routes: (router) {
      router.get('/health', HealthController.check);
      router.get('/version', VersionController.get);
    }
  );

  // Admin routes with additional middleware
  router.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

Guidelines for effective middleware usage

Do's

  • Use descriptive names for middleware clarity
  • Choose appropriate priority levels for execution order
  • Always call await next() unless terminating
  • Handle errors appropriately in middleware
  • Use dependency injection for testable middleware
  • Keep middleware focused on single responsibilities

Don'ts

  • Don't modify response after calling next()
  • Don't perform heavy computations in middleware
  • Don't swallow errors without handling them
  • Don't create circular middleware dependencies
  • Don't use blocking operations in middleware
  • Don't leak resources without proper cleanup

Need Help?

Join our community for support and discussions

Join Discord Community

On this page