Khadem Request System

Comprehensive HTTP request handling with body parsing, validation, authentication, and session management.

Basic Request Properties

dart
// Basic request properties
server.get('/api/profile', (req, res) async {
  // HTTP method
  final method = req.method; // 'GET'

  // Request path
  final path = req.path; // '/api/profile'

  // Full URI
  final uri = req.uri; // Uri object

  // Query parameters
  final page = req.query['page']; // '1'
  final limit = req.query['limit']; // '10'

  // Headers
  final userAgent = req.userAgent;
  final contentType = req.contentType;
  final isAjax = req.isAjax();
  final acceptsJson = req.acceptsJson();

  res.sendJson({
    'method': method,
    'path': path,
    'query': req.query,
    'headers': {
      'user_agent': userAgent,
      'content_type': contentType,
      'is_ajax': isAjax,
      'accepts_json': acceptsJson,
    }
  });
});

HTTP Properties

method - GET, POST, PUT, etc.
path - Request path (/api/users)
uri - Full URI object
query - Query parameters map

Header Properties

contentType - Content-Type header
userAgent - User-Agent header
acceptsJson() - Accepts JSON check
isAjax() - AJAX request check

Body Parsing

dart
// JSON request body
server.post('/api/users', (req, res) async {
  final body = await req.body;

  // Access specific fields
  final name = req.input('name');
  final email = req.input('email');
  final age = req.input('age', 18); // with default

  // Check if field exists
  if (req.has('name')) {
    // Field exists
  }

  res.sendJson({
    'received': body,
    'name': name,
    'email': email,
    'age': age,
  });
});

// Form data
server.post('/api/contact', (req, res) async {
  final formData = await req.body;

  final message = req.input('message');
  final priority = req.input('priority', 'normal');

  // Process form data...
  res.sendJson({
    'message': 'Form submitted',
    'data': formData,
  });
});

Supported Content Types

  • application/json - JSON data
  • application/x-www-form-urlencoded - Form data
  • multipart/form-data - File uploads

File Uploads

dart
// Single file upload
server.post('/api/avatar', (req, res) async {
  if (!req.hasFile('avatar')) {
    return res.badRequest().sendJson({'error': 'No avatar file provided'});
  }

  final avatarFile = req.file('avatar');

  if (avatarFile == null) {
    return res.badRequest().sendJson({'error': 'Invalid avatar file'});
  }

  // Validate file size (max 2MB)
  if (avatarFile.size > 2 * 1024 * 1024) {
    return res.badRequest().sendJson({'error': 'File too large'});
  }

  // Validate file type
  final allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
  if (!allowedTypes.contains(avatarFile.contentType)) {
    return res.badRequest().sendJson({'error': 'Invalid file type'});
  }

  // Save file
  final filename = 'avatar_${DateTime.now().millisecondsSinceEpoch}.${avatarFile.extension}';
  final path = 'storage/avatars/$filename';
  await avatarFile.saveTo(path);

  res.sendJson({
    'message': 'Avatar uploaded successfully',
    'filename': filename,
    'path': path,
  });
});

// Multiple file upload
server.post('/api/gallery', (req, res) async {
  final uploadedFiles = req.filesByName('images');

  if (uploadedFiles.isEmpty) {
    return res.badRequest().sendJson({'error': 'No images provided'});
  }

  final savedFiles = [];
  for (final file in uploadedFiles) {
    final filename = 'gallery_${DateTime.now().millisecondsSinceEpoch}_${file.filename}';
    final path = 'storage/gallery/$filename';
    await file.saveTo(path);

    savedFiles.add({
      'original_name': file.filename,
      'saved_name': filename,
      'size': file.size,
      'type': file.contentType,
    });
  }

  res.sendJson({
    'message': '${savedFiles.length} images uploaded',
    'files': savedFiles,
  });
});

File Properties

dart
final avatarFile = req.file('avatar');

// File properties
final filename = avatarFile.filename;        // 'profile.jpg'
final size = avatarFile.size;                // 1024000 (bytes)
final contentType = avatarFile.contentType;  // 'image/jpeg'
final extension = avatarFile.extension;      // 'jpg'
final fieldName = avatarFile.fieldName;      // 'avatar'

// File content
final asString = avatarFile.asString();      // File content as string
final asBytes = avatarFile.data;             // Raw bytes

File Methods

saveTo(path) - Save to disk
asString() - Get as string
size - File size in bytes
extension - File extension

Input Validation

dart
// Request validation
server.post('/api/users', (req, res) async {
  try {
    // Validate request body
    final validatedData = await req.validate({
      'name': 'required|min:2|max:100',
      'email': 'required|email|unique:users,email',
      'password': 'required|min:8',
      'age': 'optional|integer|min:18|max:120',
    });

    // Data is validated and sanitized
    final user = await User.create(validatedData);

    res.created().sendJson({'user': user});
  } catch (e) {
    // Validation failed - error response sent automatically
    // No need to handle manually
  }
});

// Custom validation with specific data
server.put('/api/users/:id', (req, res) async {
  final userId = req.param('id');
  final updateData = await req.body;

  try {
    final validatedData = req.validateData(updateData, {
      'name': 'optional|min:2|max:100',
      'email': 'optional|email',
      'bio': 'optional|max:500',
    });

    final user = await User.find(userId);
    await user.update(validatedData);

    res.sendJson({'user': user});
  } catch (e) {
    // Validation failed
  }
});

// File upload validation
server.post('/api/documents', (req, res) async {
  try {
    final validatedData = await req.validate({
      'title': 'required|min:3|max:200',
      'document': 'required|file|max:10MB|mimes:pdf,doc,docx',
      'category': 'required|in:contract,report,invoice',
    });

    final documentFile = req.file('document');
    // Process validated file...

    res.sendJson({'message': 'Document uploaded successfully'});
  } catch (e) {
    // Validation failed
  }
});

Validation Features

  • Comprehensive validation rules
  • Automatic error formatting
  • Custom validation messages
  • File upload validation

Parameters & Query Strings

dart
// Path parameters
server.get('/api/users/:id', (req, res) async {
  final userId = req.param('id'); // Get path parameter

  if (userId == null) {
    return res.badRequest().sendJson({'error': 'User ID required'});
  }

  final user = await User.find(userId);
  res.sendJson({'user': user});
});

// Multiple path parameters
server.get('/api/posts/:category/:slug', (req, res) async {
  final category = req.param('category');
  final slug = req.param('slug');

  final post = await Post.where('category', category)
    .where('slug', slug)
    .first();

  res.sendJson({'post': post});
});

// Query parameters
server.get('/api/search', (req, res) async {
  final query = req.query['q'] ?? '';
  final page = int.tryParse(req.query['page'] ?? '1') ?? 1;
  final limit = int.tryParse(req.query['limit'] ?? '10') ?? 10;
  final sort = req.query['sort'] ?? 'created_at';
  final order = req.query['order'] ?? 'desc';

  // Build search query...
  final results = await Search.query(query)
    .orderBy(sort, direction: order)
    .paginate(perPage: limit, page: page);

  res.sendJson({
    'query': query,
    'results': results.data,
    'pagination': {
      'page': results.currentPage,
      'limit': limit,
      'total': results.total,
      'pages': results.lastPage,
    }
  });
});

Path Parameters

dart
// Path parameter access
final userId = req.param('id');
final category = req.param('category');
final slug = req.param('slug');

// With default values
final page = req.param('page') ?? '1';
final limit = req.param('limit') ?? '10';

// Check if parameter exists
if (req.params.hasParam('id')) {
  // Parameter exists
}

Query Parameters

dart
// Query parameter access
final search = req.query['q'];
final page = req.query['page'];
final limit = req.query['limit'];
final sort = req.query['sort'];

// All query parameters
final allParams = req.query; // Map<String, String>

Authentication & Authorization

dart
// Authentication checks
server.get('/api/profile', (req, res) async {
  if (!req.isAuthenticated) {
    return res.unauthorized().sendJson({
      'error': 'Authentication required'
    });
  }

  final user = req.user;
  res.sendJson({'user': user});
});

// Role-based authorization
server.get('/api/admin/users', (req, res) async {
  if (!req.isAuthenticated) {
    return res.unauthorized().sendJson({'error': 'Not authenticated'});
  }

  if (!req.hasRole('admin')) {
    return res.forbidden().sendJson({'error': 'Admin access required'});
  }

  const users = await User.all();
  res.sendJson({'users': users});
});

// Multiple role check
server.get('/api/moderator/content', (req, res) async {
  if (!req.hasAnyRole(['admin', 'moderator'])) {
    return res.forbidden().sendJson({'error': 'Insufficient permissions'});
  }

  // User has admin or moderator role
  const content = await Content.needingModeration();
  res.sendJson({'content': content});
});

// Web authentication helpers
server.get('/dashboard', (req, res) async {
  if (!req.isWebAuthenticated) {
    return await res.redirect('/login');
  }

  final user = await req.getWebUser();
  const viewData = req.webViewData;

  // Render dashboard with user data
  await res.view('dashboard', data: viewData);
});

// CSRF protection
server.post('/api/posts', (req, res) async {
  if (!req.validateCsrfToken(req.input('csrf_token'))) {
    return res.forbidden().sendJson({'error': 'Invalid CSRF token'});
  }

  // Process form...
  res.sendJson({'message': 'Post created'});
});

Auth Features

  • User authentication state
  • Role-based authorization
  • Web authentication helpers
  • CSRF token validation

Session Management

dart
// Session management
server.post('/login', (req, res) async {
  final credentials = await req.body;
  final user = await authenticateUser(credentials);

  if (user != null) {
    // Store user data in session
    req.setSession('user_id', user.id);
    req.setSession('user_name', user.name);
    req.setSession('user_role', user.role);

    // Flash success message
    req.flashSession('success', 'Login successful!');

    res.sendJson({'message': 'Login successful'});
  } else {
    res.unauthorized().sendJson({'error': 'Invalid credentials'});
  }
});

// Access session data
server.get('/api/user', (req, res) async {
  final userId = req.getSession('user_id');
  final userName = req.getSession('user_name');

  if (userId == null) {
    return res.unauthorized().sendJson({'error': 'Not authenticated'});
  }

  res.sendJson({
    'user_id': userId,
    'user_name': userName,
  });
});

// Session utilities
server.get('/api/session/info', (req, res) async {
  res.sendJson({
    'session_id': req.sessionId,
    'is_empty': req.isSessionEmpty,
    'length': req.sessionLength,
    'keys': req.sessionKeys.toList(),
    'is_valid': req.isSessionValid(),
    'created_at': req.getSessionCreatedAt(),
    'last_access': req.getSessionLastAccess(),
  });
});

// Session cleanup
server.post('/logout', (req, res) async {
  req.destroySession();
  res.sendJson({'message': 'Logged out successfully'});
});

Session Methods

getSession(key) - Get value
setSession(key, value) - Set value
flashSession(key, value) - Flash data
pullSession(key) - Get & remove

Flash Messages

dart
// Flash messages
server.post('/contact', (req, res) async {
  try {
    // Process contact form
    await processContactForm(await req.body);

    req.flashSession('success', 'Message sent successfully!');
    res.sendJson({'message': 'Message sent'});
  } catch (e) {
    req.flashSession('error', 'Failed to send message');
    res.badRequest().sendJson({'error': 'Failed to send message'});
  }
});

// Retrieve flash messages
server.get('/messages', (req, res) async {
  final successMessage = req.pullSession('success');
  final errorMessage = req.pullSession('error');

  res.sendJson({
    'messages': {
      if (successMessage != null) 'success': successMessage,
      if (errorMessage != null) 'error': errorMessage,
    }
  });
});

Cookies

dart
// Cookie management
server.post('/login', (req, res) async {
  final credentials = await req.body;
  final user = await authenticateUser(credentials);

  if (user != null) {
    // Set authentication cookie
    req.cookieHandler.set(
      'auth_token',
      generateAuthToken(user),
      httpOnly: true,
      secure: true,
      maxAge: Duration(days: 7)
    );

    // Set remember token if requested
    if (req.input('remember') == true) {
      req.cookieHandler.set(
        'remember_token',
        generateRememberToken(user),
        maxAge: Duration(days: 30)
      );
    }

    res.sendJson({'message': 'Login successful'});
  }
});

// Access cookies
server.get('/api/user', (req, res) async {
  final authToken = req.cookie('auth_token');
  final rememberToken = req.rememberToken;

  if (authToken == null && rememberToken == null) {
    return res.unauthorized().sendJson({'error': 'Not authenticated'});
  }

  // Validate tokens...
  res.sendJson({'message': 'Authenticated'});
});

// Cookie utilities
server.get('/cookies', (req, res) async {
  res.sendJson({
    'all_cookies': req.cookies,
    'auth_token': req.cookie('auth_token'),
    'has_remember_token': req.hasRememberToken(),
    'has_csrf_token': req.hasCsrfToken(),
    'csrf_token': req.csrfToken,
  });
});

// Clear cookies
server.post('/logout', (req, res) async {
  req.cookieHandler.delete('auth_token');
  req.cookieHandler.delete('remember_token');

  res.sendJson({'message': 'Logged out'});
});

Cookie Features

  • Secure cookie handling
  • HttpOnly flag support
  • Automatic expiration
  • Remember tokens

Advanced Patterns

Custom Attributes

dart
// Custom attributes
server.useMiddleware((req, res, next) async {
  // Add custom data to request
  req.setAttribute('request_id', generateRequestId());
  req.setAttribute('start_time', DateTime.now());
  req.setAttribute('user_agent_parsed', parseUserAgent(req.userAgent));

  await next();
});

server.get('/api/data', (req, res) async {
  // Access custom attributes
  final requestId = req.attribute<String>('request_id');
  final startTime = req.attribute<DateTime>('start_time');
  final userAgentInfo = req.attribute<Map<String, dynamic>>('user_agent_parsed');

  // Use attributes in response
  res.sendJson({
    'request_id': requestId,
    'processing_time': DateTime.now().difference(startTime),
    'user_agent': userAgentInfo,
  });
});

Store custom data during request lifecycle

Request Context

dart
// Request context across middleware
class AuthMiddleware implements Middleware {
  @override
  Future<void> handle(Request req, Response res, NextFunction next) async {
    final token = req.header('authorization');

    if (token != null) {
      try {
        final user = await validateToken(token);
        req.setAttribute('user', user);
        req.setUser(user); // Also set in auth system
      } catch (e) {
        // Invalid token
      }
    }

    await next();
  }
}

class LoggingMiddleware implements Middleware {
  @override
  Future<void> handle(Request req, Response res, NextFunction next) async {
    final startTime = DateTime.now();
    req.setAttribute('start_time', startTime);

    await next();

    final duration = DateTime.now().difference(startTime);
    final user = req.attribute<Map<String, dynamic>>('user');

    print('Request: ${req.method} ${req.path}');
    print('User: ${user?['id'] ?? 'anonymous'}');
    print('Duration: ${duration.inMilliseconds}ms');
  }
}

Access request data across middleware and handlers

Complete API Example

dart
// Complete request handling example
import 'package:khadem/khadem_dart.dart';

class UserController {
  static Future index(Request req, Response res) async {
    // Check authentication
    if (!req.isAuthenticated) {
      return res.unauthorized().sendJson({'error': 'Not authenticated'});
    }

    // Parse query parameters
    final page = int.tryParse(req.query['page'] ?? '1') ?? 1;
    final limit = int.tryParse(req.query['limit'] ?? '10') ?? 10;
    final search = req.query['search'];

    // Build query
    var query = User.query();
    if (search != null && search.isNotEmpty) {
      query = query.where('name', 'LIKE', '%$search%')
                  .orWhere('email', 'LIKE', '%$search%');
    }

    // Get paginated results
    final users = await query.paginate(perPage: limit, page: page);

    res.sendJson({
      'data': users.data.map((user) => user.toJson()).toList(),
      'pagination': {
        'page': users.currentPage,
        'per_page': limit,
        'total': users.total,
        'last_page': users.lastPage,
        'has_next': users.currentPage < users.lastPage,
        'has_prev': users.currentPage > 1,
      }
    });
  }

  static Future store(Request req, Response res) async {
    try {
      // Validate input
      final validatedData = await req.validate({
        'name': 'required|min:2|max:100',
        'email': 'required|email|unique:users,email',
        'password': 'required|min:8|confirmed',
        'avatar': 'optional|file|max:2MB|mimes:jpeg,png,gif',
      });

      // Handle file upload if provided
      if (req.hasFile('avatar')) {
        final avatarFile = req.file('avatar')!;
        final filename = 'avatar_${DateTime.now().millisecondsSinceEpoch}.${avatarFile.extension}';
        await avatarFile.saveTo('storage/avatars/$filename');
        validatedData['avatar'] = filename;
      }

      // Hash password
      validatedData['password'] = hashPassword(validatedData['password']);

      // Create user
      final user = await User.create(validatedData);

      // Set session
      req.setSession('user_id', user.id);
      req.flashSession('success', 'Account created successfully!');

      res.created().sendJson({
        'user': user.toJson(),
        'message': 'Account created successfully'
      });

    } catch (e) {
      // Flash input for form repopulation
      req.flashInput(await req.body);
      req.flashSession('error', 'Failed to create account');

      res.badRequest().sendJson({
        'error': 'Validation failed',
        'message': 'Please check your input and try again'
      });
    }
  }

  static Future show(Request req, Response res) async {
    final userId = req.param('id');

    if (userId == null) {
      return res.badRequest().sendJson({'error': 'User ID required'});
    }

    final user = await User.find(userId);

    if (user == null) {
      return res.notFound().sendJson({'error': 'User not found'});
    }

    // Check if user can view this profile
    if (!req.isAuthenticated || (req.userId != user.id && !req.hasRole('admin'))) {
      return res.forbidden().sendJson({'error': 'Access denied'});
    }

    res.sendJson({'user': user.toJson()});
  }

  static Future update(Request req, Response res) async {
    final userId = req.param('id');

    if (userId == null) {
      return res.badRequest().sendJson({'error': 'User ID required'});
    }

    // Check ownership or admin role
    if (!req.isAuthenticated || (req.userId != userId && !req.hasRole('admin'))) {
      return res.forbidden().sendJson({'error': 'Access denied'});
    }

    try {
      final validatedData = await req.validate({
        'name': 'optional|min:2|max:100',
        'email': 'optional|email',
        'bio': 'optional|max:500',
        'avatar': 'optional|file|max:2MB|mimes:jpeg,png,gif',
      });

      // Handle avatar upload
      if (req.hasFile('avatar')) {
        final avatarFile = req.file('avatar')!;
        final filename = 'avatar_${DateTime.now().millisecondsSinceEpoch}.${avatarFile.extension}';
        await avatarFile.saveTo('storage/avatars/$filename');
        validatedData['avatar'] = filename;
      }

      final user = await User.find(userId);
      await user.update(validatedData);

      req.flashSession('success', 'Profile updated successfully');

      res.sendJson({
        'user': user.toJson(),
        'message': 'Profile updated successfully'
      });

    } catch (e) {
      res.badRequest().sendJson({
        'error': 'Validation failed',
        'message': 'Please check your input'
      });
    }
  }

  static Future destroy(Request req, Response res) async {
    final userId = req.param('id');

    if (userId == null) {
      return res.badRequest().sendJson({'error': 'User ID required'});
    }

    // Only admins can delete users
    if (!req.hasRole('admin')) {
      return res.forbidden().sendJson({'error': 'Admin access required'});
    }

    final user = await User.find(userId);

    if (user == null) {
      return res.notFound().sendJson({'error': 'User not found'});
    }

    await user.delete();

    // Clear session if user deleted themselves
    if (req.userId == userId) {
      req.destroySession();
    }

    res.sendJson({'message': 'User deleted successfully'});
  }
}

// Register routes
void registerUserRoutes(Server server) {
  server.group(
    prefix: '/api/users',
    routes: (router) {
      router.get('', UserController.index);
      router.post('', UserController.store);
      router.get('/:id', UserController.show);
      router.put('/:id', UserController.update);
      router.delete('/:id', UserController.destroy);
    }
  );
}

Best Practices

✅ Recommendations

  • Always validate input data before processing
  • Use appropriate HTTP status codes in responses
  • Handle file uploads securely with size limits
  • Validate CSRF tokens for state-changing operations
  • Use sessions for temporary user data
  • Check authentication status before accessing protected resources
  • Use flash messages for user feedback
  • Parse request body only when needed

❌ Avoid

  • Don't trust user input without validation
  • Don't store sensitive data in sessions
  • Don't parse request body multiple times unnecessarily
  • Don't expose internal errors to clients
  • Don't use cookies for sensitive authentication data
  • Don't skip CSRF protection for forms
  • Don't store large files in memory

On this page