Here’s something I love about Laravel: validation doesn’t belong in your controller. Thanks to dependency injection, you can move it into a dedicated FormRequest class and let Laravel automatically resolve and validate it before your controller even runs. It’s cleaner, more testable, more reusable, and it keeps your controller focused on what it should actually do. So, instead of writing validation inline in your controller, define rules in a dedicated class:
// app/Http/Requests/CreateLicenseRequest.php
class CreateLicenseRequest extends FormRequest
{
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:100'],
'domain' => ['required', 'string', 'max:100'],
];
}
public function messages(): array
{
return [
'name.required' => Lang::get('validation.name.required'),
'name.string' => Lang::get('validation.name.string'),
'name.max' => Lang::get('validation.name.max'),
'domain.required' => Lang::get('validation.domain.required'),
'domain.string' => Lang::get('validation.domain.string'),
'domain.max' => Lang::get('validation.domain.max'),
];
}
protected function failedValidation(Validator $validator): never
{
throw new HttpResponseException(
response: (new ErrorResponse(
message: (string) json_encode($validator->errors()->toArray()),
))->toResponse($this)
);
}
}
Here's why this matters: FormRequest isn't just a place to put validation rules. It's a system that makes your API better in concrete ways.
Centralized validation means all your rules live in one place. When someone needs to understand what data your endpoint accepts, they read the FormRequest, not hunt through a controller for inline validation logic. When you need to change the validation rules, maybe domain should accept longer strings, or maybe you need to add a new field, you update one file. Everything that uses that FormRequest automatically gets the new rules. No scattered changes across multiple controllers. No inconsistency where one endpoint validates differently than another.
Reusability comes naturally once validation is separated. The same CreateLicenseRequest can be used from multiple controllers if multiple endpoints need those exact validations. You don't copy-paste validation logic. You don't wonder if this controller has the same rules as that controller. Use the same FormRequest, get the same validation, guaranteed consistency. When requirements change, you update once and benefit everywhere that uses it.
Testability is where FormRequests really shine. You can test your validation in isolation, without going through the full HTTP request cycle. Write a test that verifies validation fails correctly when name is missing, when domain exceeds the character limit, when the format is wrong. These tests run fast because they're not spinning up your entire application. Your team can confidently modify validation rules knowing tests have their back. And your controllers stay simpler because the validation is already proven to work before it ever reaches the controller.
Localization is automatic. Your messages() method returns translation keys like 'validation.name.required'. Laravel looks these up in your language files (lang/en/validation.php, lang/es/validation.php, etc.) and returns the right error message for the user's language. Your API instantly supports multilingual validation errors without any extra wiring. A Spanish consumer gets Spanish error messages. A French consumer gets French. One system, infinite languages.
Security features are baked in. CSRF tokens are automatically validated on state-changing requests (POST, PUT, DELETE). You don't have to remember to add @csrf checks everywhere. Laravel provides other goodies too: the authorized() method lets you add authorization logic right in the FormRequest. The prepareForValidation() method lets you sanitize input before validation. These securities are defaults, they apply unless you explicitly disable them, which is the opposite of how most developers build systems (security as an afterthought).
Authorization can happen in the FormRequest itself through the authorize() method. Not every user should be able to create licenses. Maybe only admins, or only users with a specific role. Instead of scattering authorization checks across your codebase, handle them right here. You can check permissions, verify user status, validate relationships, all before your controller logic even runs. If authorization fails, the request fails at the boundary with a clear 403 Forbidden response. Your controller never has to worry about unauthorized requests reaching it.
Validate data at the boundary. Once it's past the controller, assume it's valid.
FormRequest is your first line of defense against bad data.