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 functionname
: Unique identifier for the middlewarepriority
: 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