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:
-
Authorization Check:
authorize()is called first- If it returns
false, anUnauthorizedExceptionis thrown - If it returns
true, validation continues
- If it returns
-
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
-
Validation: The request's
validate()method is called- Uses the rules from
rules()and messages frommessages() - Throws
ValidationExceptionif validation fails
- Uses the rules from
-
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
- Keep FormRequests Focused: One FormRequest per action
- Use Authorization: Always implement
authorize()for security - Provide Custom Messages: User-friendly error messages improve UX
- Normalize in passedValidation: Clean and transform data after validation
- Test Thoroughly: Write tests for validation rules and authorization
- Document Complex Rules: Add comments for complex validation logic
- Handle Files Properly: Use appropriate file validation rules
- Use Helper Methods: Leverage
only(),except()for data manipulation
Constructors
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
- 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< errors) → voidString> > - 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