Middleware System
Comprehensive middleware framework with priority-based execution, error handling, and flexible registration patterns.
Overview
Understanding the middleware pipeline
Core Features
Execution Flow
Creating Middleware
Implement custom middleware classes
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
handlernamepriorityPriority System
Control middleware execution order
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
CORS, logging, security headers
Route resolution, URL rewriting
Authentication, authorization
Input validation, sanitization
App-specific logic
Response formatting, cleanup
Registration Patterns
Different ways to register middleware
Global Registration
// 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
// 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 routesApplies only to specific routes or route groups
Group Registration
// 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
// 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
// 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
// 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
// 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
// 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
// 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
Complete Example
Full middleware implementation with all features
// 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
