FormRequest class abstract

FormRequest provides a clean, reusable way to encapsulate validation logic, authorization checks, and data preparation for HTTP requests.

FormRequest classes are designed to be used with controllers to separate validation concerns from business logic. They provide a standardized way to handle form submissions with proper error handling and data transformation.

Basic Usage

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

  @override
  Map<String, String> messages() {
    return {
      'email.unique': 'This email is already taken',
      'password.confirmed': 'Password confirmation does not match',
    };
  }

  @override
  bool authorize(Request request) {
    return request.user()?.can('create-users') ?? false;
  }

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

// In controller:
static Future<void> store(Request req, Response res) async {
  try {
    final validated = await CreateUserRequest().validate(req);
    final user = await User.create(validated);
    res.status(201).sendJson({'user': user});
  } on UnauthorizedException catch (e) {
    res.status(403).sendJson({'error': e.message});
  } on ValidationException catch (e) {
    res.status(422).sendJson({'errors': e.errors});
  }
}

Lifecycle

FormRequest follows a specific lifecycle when validate() is called:

  1. Authorization Check: authorize() is called first

    • If it returns false, an UnauthorizedException is thrown
    • If it returns true, validation continues
  2. Preparation: prepareForValidation() is called

    • Use this to modify or clean input data before validation
    • Changes made here affect validation but not the final validated data
  3. Validation: The request's validate() method is called

    • Uses the rules from rules() and messages from messages()
    • Throws ValidationException if validation fails
  4. Post-Validation: passedValidation() is called

    • Use this to transform validated data (hashing passwords, etc.)
    • Changes made here are included in the final validated data

Available Methods

Abstract Methods (Must Override)

rules() - Required

Define validation rules for the request.

@override
Map<String, String> rules() {
  return {
    'field_name': 'required|email|max:255',
  };
}

Optional Methods

messages()

Define custom error messages for validation rules.

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

authorize(Request request)

Check if the user is authorized to make this request.

@override
bool authorize(Request request) {
  return request.user()?.isAdmin ?? false;
}

prepareForValidation(Request request)

Prepare the request data for validation.

@override
void prepareForValidation(Request request) {
  // Trim whitespace, normalize data, etc.
  // Note: Request class may not have merge() method
}

passedValidation(Map<String, dynamic> validated)

Process data after successful validation.

@override
void passedValidation(Map<String, dynamic> validated) {
  validated['password'] = Hash.make(validated['password']);
  validated['created_at'] = DateTime.now().toIso8601String();
}

failedValidation(Map<String, String> errors)

Handle validation failure. Default behavior throws ValidationException.

@override
void failedValidation(Map<String, String> errors) {
  // Log errors, send notifications, etc.
  logger.warning('Validation failed', errors);
  super.failedValidation(errors); // Re-throws the exception
}

Helper Methods

validatedData

Get all validated data after validation.

final data = formRequest.validatedData;

validatedInput(key, {defaultValue})

Get a specific validated field value.

final email = formRequest.validatedInput('email');
final name = formRequest.validatedInput('name', defaultValue: 'Guest');

only(List<String> keys)

Get only specified fields from validated data.

final credentials = formRequest.only(['email', 'password']);

except(List<String> keys)

Get all validated data except specified fields.

final safeData = formRequest.except(['password', 'token']);

hasValidated()

Check if validation has been performed.

if (formRequest.hasValidated()) {
  // Safe to access validated data
}

request

Get the underlying Request instance.

final originalRequest = formRequest.request;

Exception Handling

ValidationException

Thrown when validation fails. Contains field-specific error messages.

try {
  await CreateUserRequest().validate(req);
} on ValidationException catch (e) {
  // e.errors is Map<String, String> of field => error message
  res.status(422).sendJson({'errors': e.errors});
}

UnauthorizedException

Thrown when authorize() returns false.

try {
  await CreateUserRequest().validate(req);
} on UnauthorizedException catch (e) {
  res.status(403).sendJson({'error': e.message});
}

Testing

test('validates user creation', () async {
  final request = createTestRequest({
    'name': 'John Doe',
    'email': 'john@example.com',
    'password': 'password123',
  });

  final formRequest = CreateUserRequest();
  final validated = await formRequest.validate(request);

  expect(validated['name'], equals('John Doe'));
  expect(validated['email'], equals('john@example.com'));
});

test('fails validation with invalid data', () async {
  final request = createTestRequest({
    'name': '',
    'email': 'invalid-email',
  });

  final formRequest = CreateUserRequest();

  expect(
    () => formRequest.validate(request),
    throwsA(isA<ValidationException>()),
  );
});

Best Practices

  1. Keep FormRequests Focused: One FormRequest per action
  2. Use Authorization: Always implement authorize() for security
  3. Provide Custom Messages: User-friendly error messages improve UX
  4. Normalize in passedValidation: Clean and transform data after validation
  5. Test Thoroughly: Write tests for validation rules and authorization
  6. Document Complex Rules: Add comments for complex validation logic
  7. Handle Files Properly: Use appropriate file validation rules
  8. Use Helper Methods: Leverage only(), except() for data manipulation

Constructors

FormRequest()

Properties

hashCode int
The hash code for this object.
no setterinherited
request Request?
Get the original request instance that was validated.
no setter
runtimeType Type
A representation of the runtime type of the object.
no setterinherited
validatedData Map<String, dynamic>?
Get all validated data after successful validation.
no setter

Methods

authorize(Request request) bool
Determine if the user is authorized to make this request.
except(List<String> keys) Map<String, dynamic>
Get all validated data except the specified fields.
failedValidation(Map<String, List<String>> errors) → void
Handle validation failure.
hasValidated() bool
Check if validation has been performed successfully.
merge(Map<String, dynamic> values) → void
Merges values into the request body.
messages() Map<String, String>
Define custom error messages for validation rules.
noSuchMethod(Invocation invocation) → dynamic
Invoked when a nonexistent method or property is accessed.
inherited
only(List<String> keys) Map<String, dynamic>
Get only the specified fields from validated data.
passedValidation(Map<String, dynamic> validated) → void
Process data after successful validation.
prepareForValidation(Request request) Future<void>
Prepare the request data for validation.
rules() Map<String, dynamic>
Define the validation rules for this request.
toString() String
A string representation of this object.
inherited
validate(Request request) Future<Map<String, dynamic>>
Validate the request using the defined rules and authorization.
validated() Map<String, dynamic>
Get all validated data after successful validation.
validatedInput(String key, {dynamic defaultValue}) → dynamic
Get a specific validated field value.

Operators

operator ==(Object other) bool
The equality operator.
inherited