Validation

A comprehensive guide to input validation in Khadem. Learn how to validate user input, create custom validation rules, use Form Request classes, and handle validation errors effectively.

Input ValidationError HandlingForm RequestsCustom Rules

Introduction

Validation is a critical component of any web application. Khadem provides a robust and intuitive validation system inspired by Laravel, allowing you to validate incoming HTTP requests with ease. The validation system is designed to be:

Easy to Use

Simple, expressive syntax that makes validation rules easy to read and write. Chain multiple rules with the pipe character.

Powerful

50+ built-in validation rules covering common use cases, from basic type checking to complex file validation.

Extensible

Create custom validation rules tailored to your application's specific business logic and requirements.

Type-Safe

Leverage Dart's type system with automatic type conversion and validation for a safer codebase.

Quick Start

The most common way to validate incoming requests in Khadem is by calling the validate() method on the request object. This method accepts a map of validation rules and automatically throws a ValidationException if validation fails, which you can catch and handle appropriately.

Here's a complete example of validating user registration data:

Basic Request Validation

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

class UserController {
  static Future store(Request req, Response res) async {
    try {
      // Validate request data (excluding database rules for now)
      final validatedData = await req.validate({
        'name': 'required|string|max:255',
        'email': 'required|email', // Note: unique validation done manually below
        'password': 'required|string|min:8|confirmed',
        'age': 'nullable|int|min:18|max:120',
      });

      // Manual database uniqueness check
      final existingUser = await User.where('email', validatedData['email']).first();
      if (existingUser != null) {
        res.status(422).sendJson({
          'error': true,
          'message': 'Validation failed.',
          'status_code': 422,
          'timestamp': DateTime.now().toIso8601String(),
          'details': {
            'email': 'This email address is already registered.',
          },
          'exception_type': 'ValidationException'
        });
        return;
      }

      // Create user with validated data
      final user = User(
        name: validatedData['name'],
        email: validatedData['email'],
        password: Hash.make(validatedData['password']),
        age: validatedData['age'],
      );

      await user.save();

      res.status(201).sendJson({
        'success': true,
        'message': 'User created successfully',
        'data': {
          'user': user.toJson(),
        }
      });
    } on ValidationException catch (e) {
      res.status(422).sendJson({
        'error': true,
        'message': 'Validation failed.',
        'status_code': 422,
        'timestamp': DateTime.now().toIso8601String(),
        'details': e.errors,
        'exception_type': 'ValidationException'
      });
    } catch (e) {
      res.status(500).sendJson({
        'error': true,
        'message': 'An unexpected error occurred',
        'status_code': 500,
        'timestamp': DateTime.now().toIso8601String(),
        'exception_type': e.runtimeType.toString()
      });
    }
  }
}

How It Works

  • Validation Rules: Define rules using pipe-separated strings (e.g., 'required|email|max:255')
  • Automatic Validation: The validate() method automatically validates input and throws ValidationException on failure
  • Type Safety: Validated data is returned as a Map<String, dynamic> containing only the validated fields
  • Error Handling: Catch ValidationException to handle validation errors gracefully

Important Notes

  • Validation rules follow Laravel-inspired syntax for familiarity
  • Database validation rules like unique and exists must be implemented manually
  • Custom messages can be provided to override default error messages
  • Only validated fields are returned in the result

Available Validation Rules

Basic Rules

requiredField must be present
nullableField can be null
stringMust be a string
intMust be an integer
numericMust be numeric
boolMust be boolean
urlMust be valid URL
active_urlURL must be accessible
ipMust be valid IP
ipv4Must be valid IPv4
ipv6Must be valid IPv6

Size & Comparison Rules

min:valueMinimum value/length
max:valueMaximum value/length
alphaAlphabetic characters only
alpha_numAlphanumeric characters only
alpha_dashAlphanumeric, dashes, underscores
starts_with:valMust start with specified value
ends_with:valMust end with specified value
regex:patternMust match regex pattern
uuidMust be valid UUID
jsonMust be valid JSON
phoneMust be valid phone number

Database Rules

unique:table,columnUnique in database (not implemented)
exists:table,columnMust exist in database (not implemented)

⚠️ Note: Database validation rules are not yet implemented in Khadem. For now, perform database uniqueness and existence checks manually in your controllers.

Date & Time Rules

dateMust be valid date
date_format:formatDate must match format
before:dateMust be before specified date
after:dateMust be after specified date

Array & Collection Rules

arrayMust be an array
min_items:NMinimum number of items
max_items:NMaximum number of items
distinctItems must be unique
in_array:fieldMust exist in specified array
not_in_array:fieldMust not exist in specified array

File & Upload Rules

fileMust be a valid file
imageMust be an image file
mimes:ext1,ext2Must have specified extensions
max_file_size:NMaximum file size in KB

Conditional Rules

sometimesValidate only if present
required_if:field,valueRequired if other field has value
prohibitedField must not be present
prohibited_if:field,valueProhibited if other field has value

Email & Confirmation Rules

emailMust be valid email address
confirmedMust match field_confirmation
in:val1,val2,val3Must be one of specified values

Validation Tips & Best Practices

Combining Rules

Use the pipe | character to chain multiple rules:

'email': 'required|email|max:255'

Nullable vs Optional

nullable allows null values, while omitting required makes a field optional.

Array Validation

Use .* notation to validate array items:

'tags.*': 'required|string|max:50'

Rule Order Matters

Place nullable first, and type-checking rules (like int, string) before size rules.

Manual Validation

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

class UserController {
  static Future update(Request req, Response res, int id) async {
    try {
      // Get request body data
      final bodyData = await req.body;

      // Validate data manually (excluding database rules for now)
      final validatedData = req.validateData(bodyData, {
        'name': 'required|string|max:255',
        'email': 'required|email', // Note: unique validation done manually below
      });

      // Manual database uniqueness check (excluding current user)
      final existingUser = await User.where('email', validatedData['email'])
          .where('id', '!=', id)
          .first();
      if (existingUser != null) {
        res.status(422).sendJson({
          'error': true,
          'message': 'Validation failed.',
          'status_code': 422,
          'timestamp': DateTime.now().toIso8601String(),
          'details': {
            'email': 'This email address is already registered.',
          },
          'exception_type': 'ValidationException'
        });
        return;
      }

      // Update user
      final user = await User.find(id);
      user.name = validatedData['name'];
      user.email = validatedData['email'];
      await user.save();

      res.sendJson({
        'success': true,
        'message': 'User updated successfully',
        'data': {
          'user': user.toJson(),
        }
      });
    } on ValidationException catch (e) {
      res.status(422).sendJson({
        'error': true,
        'message': 'Validation failed.',
        'status_code': 422,
        'timestamp': DateTime.now().toIso8601String(),
        'details': e.errors,
        'exception_type': 'ValidationException'
      });
    } catch (e) {
      res.status(500).sendJson({
        'error': true,
        'message': 'An unexpected error occurred',
        'status_code': 500,
        'timestamp': DateTime.now().toIso8601String(),
        'exception_type': e.runtimeType.toString()
      });
    }
  }
}

When to Use Manual Validation

  • • Complex validation logic that can't be expressed with rules
  • • Conditional validation based on other field values
  • • Need more control over validation flow
  • • Custom error handling requirements

Custom Validation Messages

While Khadem provides sensible default error messages for all validation rules, you often want to provide more user-friendly, context-specific messages that match your application's tone and help users understand exactly what they need to fix. Custom messages make your validation errors more helpful and professional.

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

class UserController {
  static Future store(Request req, Response res) async {
    try {
      // Validate with custom error messages
      final validatedData = await req.validate(
        {
          'email': 'required|email',
          'password': 'required|min:8|confirmed',
          'age': 'required|int|min:18',
          'profile_picture': 'nullable|image|max:2048',
          'terms': 'required|accepted',
        },
        messages: {
          // Pattern: 'field.ruleName' => 'Custom message'
          'email.required': 'We need your email address to create your account',
          'email.email': 'Please enter a valid email address (e.g., user@example.com)',
          'password.required': 'A password is required to secure your account',
          'password.min': 'Your password must be at least 8 characters long for security',
          'password.confirmed': 'The password confirmation does not match. Please try again',
          'age.required': 'Please provide your age to continue',
          'age.int': 'Age must be a valid number',
          'age.min': 'You must be at least 18 years old to register',
          'profile_picture.image': 'Profile picture must be an image file (jpg, png, gif, etc.)',
          'profile_picture.max': 'Profile picture size cannot exceed 2MB (2048KB)',
          'terms.required': 'You must accept the terms and conditions to proceed',
          'terms.accepted': 'Please check the box to accept our terms and conditions',
        },
      );

      // Process validated data
      final user = await User.create(validatedData);

      res.status(201).sendJson({
        'success': true,
        'message': 'User created successfully',
        'data': {'user': user.toJson()}
      });
    } on ValidationException catch (e) {
      // Error response with custom messages
      res.status(422).sendJson({
        'error': true,
        'message': 'Validation failed.',
        'status_code': 422,
        'timestamp': DateTime.now().toIso8601String(),
        'details': e.errors, // Will contain your custom messages
        'exception_type': 'ValidationException'
      });
    }
  }
}

// Example Error Response with Custom Messages:
// {
//   "error": true,
//   "message": "Validation failed.",
//   "status_code": 422,
//   "timestamp": "2025-10-21T16:30:15.123456",
//   "details": {
//     "email": "We need your email address to create your account",
//     "password": "Your password must be at least 8 characters long for security",
//     "age": "You must be at least 18 years old to register",
//     "terms": "You must accept the terms and conditions to proceed"
//   },
//   "exception_type": "ValidationException"
// }

Custom Message Pattern

Custom messages use a simple naming convention: field.ruleName

email.requiredCustom message for when email field fails required rule
password.minCustom message for when password field fails min rule
age.intCustom message for when age field fails int rule

Best Practices for Custom Messages

  • Be specific: Tell users exactly what's wrong and how to fix it
  • Be friendly: Use a conversational tone that matches your brand
  • Provide examples: Show users what a valid value looks like
  • Be consistent: Use the same tone and style across all messages
  • Avoid technical jargon: Make messages understandable to all users

💡 Use Case Example

Instead of the generic message "The email field is required", you might say:

"We need your email address to send you order updates and account notifications"

This explains why the field is required, making users more likely to provide the information.

Custom Validation Rules

dart
// lib/src/validation/strong_password_rule.dart
import 'package:khadem/src/contracts/validation/rule.dart';

class StrongPasswordRule implements Rule {
  @override
  String? validate(String field, dynamic value, String? arg, {required Map<String, dynamic> data}) {
    if (value == null || value.isEmpty) {
      return null; // Let required rule handle this
    }

    if (value.length < 8) {
      return 'Password must be at least 8 characters';
    }

    if (!RegExp(r'[A-Z]').hasMatch(value)) {
      return 'Password must contain at least one uppercase letter';
    }

    if (!RegExp(r'[a-z]').hasMatch(value)) {
      return 'Password must contain at least one lowercase letter';
    }

    if (!RegExp(r'[0-9]').hasMatch(value)) {
      return 'Password must contain at least one number';
    }

    return null; // Valid
  }
}
dart
// lib/src/core/validation/validation_service_provider.dart
import 'package:khadem/src/core/validation/validation_rule_repository.dart';
import '../../validation/strong_password_rule.dart';

class ValidationServiceProvider {
  static void registerCustomRules() {
    // Register custom validation rule
    ValidationRuleRepository.register('strong_password', () => StrongPasswordRule());
  }
}

// Using the custom rule
class UserController {
  static Future create(Request req, Response res) async {
    try {
      final validatedData = await req.validate({
        'password': 'required|strong_password',
        'email': 'required|email',
      });

      res.sendJson({
        'success': true,
        'message': 'User validated successfully'
      });
    } catch (e) {
      if (e is ValidationException) {
        res.statusCode(422).sendJson({
          'success': false,
          'errors': e.errors
        });
      }
    }
  }
}

Custom Rule Benefits

  • • Reusable validation logic across your application
  • • Complex business rules validation
  • • Consistent error messages
  • • Easy to test and maintain

Form Request Classes

For complex validation scenarios or when you want to reuse validation logic across multiple controllers, Khadem provides Form Request classes. These classes encapsulate validation rules, authorization logic, custom messages, and data preparation in a single, reusable component.

Form Requests follow a powerful lifecycle that gives you fine-grained control over the validation process:

Form Request Lifecycle

1

Authorization Check

The authorize() method is called first to check if the user has permission to make this request

2

Input Preparation

The prepareForValidation() method allows you to clean, normalize, or modify input before validation

3

Validation

Rules defined in rules() are applied using custom messages from messages()

4

Post-Validation Processing

The passedValidation() method is called after successful validation to transform validated data

Creating a Form Request Class

dart
// lib/src/http/requests/create_user_request.dart
import 'package:khadem/khadem.dart';

class CreateUserRequest extends FormRequest {
  @override
  Map<String, String> rules() {
    return {
      'name': 'required|string|max:255',
      'email': 'required|email',
      'password': 'required|string|min:8|confirmed',
      'role': 'required|in:admin,user,moderator',
    };
  }

  @override
  Map<String, String> messages() {
    return {
      'name.required': 'Please provide your name',
      'email.email': 'Please provide a valid email address',
      'password.min': 'Password must be at least 8 characters',
      'role.in': 'Invalid role selected',
    };
  }

  @override
  void prepareForValidation(Request request) {
    // Called before validation - access request data here
    // Use this to normalize or clean data before validation runs
  }

  @override
  void passedValidation(Map<String, dynamic> validated) {
    // Called after successful validation
    // Modify the validated data directly
    validated['password'] = Hash.make(validated['password']);
    validated['created_at'] = DateTime.now().toIso8601String();
  }

  @override
  bool authorize(Request request) {
    // Check if user is authorized to make this request
    return request.user()?.can('create-users') ?? false;
  }
}

Method Overview

  • rules() - Define validation rules (required)
  • messages() - Custom error messages (optional)
  • authorize() - Authorization logic (optional, defaults to true)
  • prepareForValidation() - Pre-processing (optional)
  • passedValidation() - Post-processing (optional)

Benefits

  • • Keeps controllers clean and focused
  • • Reusable validation logic
  • • Easy to test independently
  • • Type-safe validated data
  • • Centralized authorization

Using Form Requests in Controllers

dart
// lib/src/http/controllers/user_controller.dart
class UserController {
  static Future store(Request req, Response res) async {
    try {
      // Create FormRequest instance and validate
      final formRequest = CreateUserRequest();
      final validatedData = await formRequest.validate(req);

      // Authorization and validation automatically handled by FormRequest
      // validatedData contains validated fields with passedValidation() modifications
      
      // Manual database uniqueness check (if needed)
      final existingUser = await User.where('email', validatedData['email']).first();
      if (existingUser != null) {
        res.status(422).sendJson({
          'error': true,
          'message': 'Validation failed.',
          'status_code': 422,
          'timestamp': DateTime.now().toIso8601String(),
          'details': {
            'email': 'This email address is already registered.',
          },
          'exception_type': 'ValidationException'
        });
        return;
      }

      final user = User(
        name: validatedData['name'],
        email: validatedData['email'],
        password: validatedData['password'], // Already hashed in passedValidation()
        role: validatedData['role'],
        created_at: validatedData['created_at'], // Added in passedValidation()
      );

      await user.save();

      res.status(201).sendJson({
        'success': true,
        'message': 'User created successfully',
        'data': {
          'user': user.toJson(),
        }
      });
    } on UnauthorizedException catch (e) {
      res.status(403).sendJson({
        'error': true,
        'message': e.message,
        'status_code': 403,
        'timestamp': DateTime.now().toIso8601String(),
        'exception_type': 'UnauthorizedException'
      });
    } on ValidationException catch (e) {
      res.status(422).sendJson({
        'error': true,
        'message': 'Validation failed.',
        'status_code': 422,
        'timestamp': DateTime.now().toIso8601String(),
        'details': e.errors,
        'exception_type': 'ValidationException'
      });
    }
  }
}

Key Features of Form Requests

Centralized Validation

All validation logic for a specific action in one place

Custom Messages

Override default error messages with user-friendly alternatives

Authorization Built-in

Check permissions before validation even runs

Data Transformation

Modify data before and after validation automatically

Error Handling

dart
class UserController {
  static Future store(Request req, Response res) async {
    try {
      final validatedData = await req.validate({
        'name': 'required|string|max:255',
        'email': 'required|email',
        'password': 'required|string|min:8',
      });

      // Manual database uniqueness check
      final existingUser = await User.where('email', validatedData['email']).first();
      if (existingUser != null) {
        // Return custom validation error for duplicate email
        res.status(422).sendJson({
          'error': true,
          'message': 'Validation failed.',
          'status_code': 422,
          'timestamp': DateTime.now().toIso8601String(),
          'details': {
            'email': 'This email address is already registered.',
          },
          'exception_type': 'ValidationException'
        });
        return;
      }

      // Process validated data
      final user = User(
        name: validatedData['name'],
        email: validatedData['email'],
        password: Hash.make(validatedData['password']),
      );

      await user.save();

      // Success response
      res.status(201).sendJson({
        'success': true,
        'message': 'User created successfully',
        'data': {
          'user': user.toJson(),
        }
      });
    } on ValidationException catch (e) {
      // Khadem automatically formats ValidationException as:
      // {
      //   "error": true,
      //   "message": "Validation failed.",
      //   "status_code": 422,
      //   "timestamp": "2025-10-21T16:22:50.456153",
      //   "details": { "field": "error message" },
      //   "exception_type": "ValidationException"
      // }
      res.status(422).sendJson({
        'error': true,
        'message': e.message,
        'status_code': 422,
        'timestamp': DateTime.now().toIso8601String(),
        'details': e.errors,
        'exception_type': 'ValidationException'
      });
    } on UnauthorizedException catch (e) {
      res.status(403).sendJson({
        'error': true,
        'message': e.message,
        'status_code': 403,
        'timestamp': DateTime.now().toIso8601String(),
        'exception_type': 'UnauthorizedException'
      });
    } catch (e) {
      // Handle unexpected errors
      res.status(500).sendJson({
        'error': true,
        'message': 'An unexpected error occurred',
        'status_code': 500,
        'timestamp': DateTime.now().toIso8601String(),
        'exception_type': e.runtimeType.toString()
      });
    }
  }
}

When validation fails, Khadem returns a standardized JSON error response with HTTP status code 422 Unprocessable Entity. This response includes detailed information about what went wrong, making it easy for frontend applications to display appropriate error messages to users.

Validation Error Response Format

{
  "error": true,
  "message": "Validation failed.",
  "status_code": 422,
  "timestamp": "2025-10-21T16:22:50.456153",
  "details": {
    "email": "The email field is required.",
    "password": "The password must be at least 8 characters.",
    "age": "The age must be at least 18."
  },
  "exception_type": "ValidationException"
}
Response Fields Explained
errorAlways true for error responses
messageA general error message describing the type of error
status_codeHTTP status code (422 for validation errors)
timestampISO 8601 timestamp when the error occurred
detailsObject containing field-specific error messages (key = field name, value = error message)
exception_typeThe type of exception that was thrown
Frontend Integration Tip

The details object makes it easy to display field-specific errors in your frontend. Simply map each key to the corresponding form field and display the error message to the user.

Advanced Examples

Beyond basic field validation, Khadem supports advanced validation scenarios including conditional rules, array/nested data validation, and comprehensive file upload validation with size, type, and dimension checks.

Conditional Validation

Apply different validation rules based on the values of other fields. Perfect for forms where certain fields are only required when specific options are selected.

dart
class UserController {
  static Future updateProfile(Request req, Response res) async {
    try {
      final bodyData = await req.body;
      final rules = {
        'name': 'required|string|max:255',
        'email': 'required|email', // Note: unique validation done manually below
      };

      // Add password validation only if provided
      if (req.has('password') && req.input('password')?.isNotEmpty == true) {
        rules['password'] = 'required|string|min:8|confirmed';
        rules['password_confirmation'] = 'required';
      }

      // Add company validation for business users
      if (req.input('account_type') == 'business') {
        rules['company_name'] = 'required|string|max:255';
        rules['tax_id'] = 'required|string|size:9';
      }

      final validatedData = req.validateData(bodyData, rules);

      // Manual database uniqueness check (excluding current user)
      final existingUser = await User.where('email', validatedData['email'])
          .where('id', '!=', req.user?.id ?? 0)
          .first();
      if (existingUser != null) {
        res.status(422).sendJson({
          'error': true,
          'message': 'Validation failed.',
          'status_code': 422,
          'timestamp': DateTime.now().toIso8601String(),
          'details': {
            'email': 'This email address is already registered.',
          },
          'exception_type': 'ValidationException'
        });
        return;
      }

      // Process validated data...
      res.sendJson({
        'success': true,
        'message': 'Profile updated successfully',
        'data': {
          'user': req.user?.toJson(),
        }
      });
    } on ValidationException catch (e) {
      res.status(422).sendJson({
        'error': true,
        'message': 'Validation failed.',
        'status_code': 422,
        'timestamp': DateTime.now().toIso8601String(),
        'details': e.errors,
        'exception_type': 'ValidationException'
      });
    } catch (e) {
      res.status(500).sendJson({
        'error': true,
        'message': 'An unexpected error occurred',
        'status_code': 500,
        'timestamp': DateTime.now().toIso8601String(),
        'exception_type': e.runtimeType.toString()
      });
    }
  }
}
Common Use Cases:
  • • Password fields only required when actually changing password
  • • Business information only required for business account types
  • • Shipping address only required when different from billing address
  • • Different validation rules for different user roles

Array Validation

Validate arrays and nested object structures with ease. Khadem supports validating entire arrays as well as individual items within arrays using dot notation.

dart
class ProductController {
  static Future store(Request req, Response res) async {
    try {
      final validatedData = await req.validate({
        'name': 'required|string|max:255',
        'price': 'required|numeric|min:0',
        'categories': 'required|array|min_items:1',
        'categories.*': 'int', // Note: exists validation done manually below
        'images': 'nullable|array|max_items:5',
        'images.*': 'file|mimes:jpeg,png,jpg|max_file_size:2048',
        'specifications': 'nullable|array',
        'specifications.*.name': 'required|string|max:100',
        'specifications.*.value': 'required|string|max:255',
      });

      // Manual database existence check for categories
      final categoryIds = validatedData['categories'] as List;
      for (final categoryId in categoryIds) {
        final category = await Category.find(categoryId);
        if (category == null) {
          res.status(422).sendJson({
            'error': true,
            'message': 'Validation failed.',
            'status_code': 422,
            'timestamp': DateTime.now().toIso8601String(),
            'details': {
              'categories': 'One or more selected categories do not exist.',
            },
            'exception_type': 'ValidationException'
          });
          return;
        }
      }

      // Data structure:
      // {
      //   'name': 'Product Name',
      //   'price': 99.99,
      //   'categories': [1, 2, 3],
      //   'images': [/* uploaded files */],
      //   'specifications': [
      //     {'name': 'Color', 'value': 'Red'},
      //     {'name': 'Size', 'value': 'Large'}
      //   ]
      // }

      // Process validated data...
      final product = await Product.create(validatedData);

      res.status(201).sendJson({
        'success': true,
        'message': 'Product created successfully',
        'data': {
          'product': product.toJson(),
        }
      });
    } on ValidationException catch (e) {
      res.status(422).sendJson({
        'error': true,
        'message': 'Validation failed.',
        'status_code': 422,
        'timestamp': DateTime.now().toIso8601String(),
        'details': e.errors,
        'exception_type': 'ValidationException'
      });
    } catch (e) {
      res.status(500).sendJson({
        'error': true,
        'message': 'An unexpected error occurred',
        'status_code': 500,
        'timestamp': DateTime.now().toIso8601String(),
        'exception_type': e.runtimeType.toString()
      });
    }
  }
}
Array Validation Patterns:
tagsValidates the entire array (e.g., required, min items, max items)
tags.*Validates each item in the array (e.g., each tag must be a string)
items.*.nameValidates nested properties within array objects

File Upload Validation

Comprehensive file upload validation including file type (mimes), size limits, image dimensions, and more. Ensure uploaded files meet your application's requirements before processing.

dart
class FileUploadController {
  static Future upload(Request req, Response res) async {
    try {
      final validatedData = await req.validate({
        'document': 'required|file|mimes:pdf,doc,docx|max_file_size:5120', // 5MB
        'images': 'nullable|array|max_items:3',
        'images.*': 'file|mimes:jpeg,png,jpg,gif|max_file_size:2048', // 2MB each
        'avatar': 'nullable|file|image|mimes:jpeg,png,jpg|max_file_size:1024',
      });

      // Access uploaded files
      final document = req.file('document');
      final images = req.filesByName('images'); // Array of files
      final avatar = req.file('avatar');

      // Process files...
      if (document != null) {
        await document.saveTo('/uploads/documents/${document.filename}');
      }

      if (avatar != null) {
        await avatar.saveTo('/uploads/avatars/user_${req.user?.id ?? 0}.jpg');
      }

      res.sendJson({
        'success': true,
        'message': 'Files uploaded successfully',
        'data': {
          'document': document?.filename,
          'avatar': avatar?.filename,
          'images_count': images?.length ?? 0,
        }
      });
    } on ValidationException catch (e) {
      res.status(422).sendJson({
        'error': true,
        'message': 'Validation failed.',
        'status_code': 422,
        'timestamp': DateTime.now().toIso8601String(),
        'details': e.errors,
        'exception_type': 'ValidationException'
      });
    } catch (e) {
      res.status(500).sendJson({
        'error': true,
        'message': 'An unexpected error occurred',
        'status_code': 500,
        'timestamp': DateTime.now().toIso8601String(),
        'exception_type': e.runtimeType.toString()
      });
    }
  }
}
File Validation Rules:
fileEnsures the field contains an uploaded file
imageFile must be an image (jpg, jpeg, png, gif, bmp, svg, webp)
mimes:jpg,pngRestricts allowed file types by extension
max:2048Maximum file size in kilobytes (2048 = 2MB)
dimensionsValidate image dimensions (min_width, max_width, min_height, max_height, ratio)

On this page