DEV Community

A0mineTV
A0mineTV

Posted on

Laravel Form Requests: Stop Cluttering Your Controllers with Validation

Validation is one of those "small" things that can slowly ruin a codebase. At first, it is just a couple of request()->validate() calls inside your controllers, but as you add more fields and endpoints, you end up with duplicated validation arrays and controllers full of conditionals.

Laravel Form Requests solve this by moving validation and authorization into dedicated classes.

At first, it’s a couple of request()->validate() calls inside controllers.
Then you add more fields, more endpoints, more rules… and suddenly you have:

  • duplicated validation arrays,
  • controllers full of conditionals,
  • inconsistent error messages,
  • and “who is allowed to do this?” checks mixed with validation logic.

The Philosophy: Controllers Orchestrate, Form Requests Validate

A Form Request is a custom class that centralizes:

  • rules(): Your validation logic.
  • authorize(): Who is allowed to perform the action.
  • messages(): Custom error messages.
  • prepareForValidation(): Normalizing data before rules are applied.

In short:

Controllers orchestrate. Form Requests validate (and can authorize).


Why use Form Requests?

1) Keep controllers clean

Controllers stay focused on what to do, not how to validate.

2) Reuse rules safely

A StorePostRequest and UpdatePostRequest can share or extend rules without copy/paste.

3) Centralize authorization for an action

authorize() can be a great companion to Policies:

  • Policy decides resource permissions
  • Form Request decides action permission (e.g. “can create?”)

4) Easier testing

Form Requests make it straightforward to test validation and edge cases.


Creating a Form Request

php artisan make:request StoreProjectRequest
Enter fullscreen mode Exit fullscreen mode

You’ll get something like:

// app/Http/Requests/StoreProjectRequest.php
namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class StoreProjectRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
        return [];
    }
}
Enter fullscreen mode Exit fullscreen mode

Basic example: store a Project

Form Request

// app/Http/Requests/StoreProjectRequest.php
namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;

class StoreProjectRequest extends FormRequest
{
    public function authorize(): bool
    {
        // Option A: keep it simple
        return auth()->check();

        // Option B: delegate to a Policy (recommended in many apps)
        // return $this->user()->can('create', Project::class);
    }

    public function rules(): array
    {
        return [
            'name'        => ['required', 'string', 'min:3', 'max:120'],
            'description' => ['nullable', 'string', 'max:2000'],
            'status'      => ['required', Rule::in(['draft', 'active'])],
            'starts_at'   => ['nullable', 'date'],
        ];
    }
}
Enter fullscreen mode Exit fullscreen mode

Controller stays small

// app/Http/Controllers/ProjectController.php
use App\Http\Requests\StoreProjectRequest;

class ProjectController
{
    public function store(StoreProjectRequest $request)
    {
        $data = $request->validated();

        // ... create logic (model/service/use case)
        // Project::create([...$data, 'user_id' => $request->user()->id]);

        return redirect()->back()->with('status', 'Project created!');
    }
}
Enter fullscreen mode Exit fullscreen mode

Notice the main win:

  • validation is not in the controller
  • $request->validated() guarantees only valid fields go through

Improve error messages (optional)

public function messages(): array
{
    return [
        'name.required' => 'Please provide a project name.',
        'name.min'      => 'Project name must be at least :min characters.',
    ];
}

public function attributes(): array
{
    return [
        'starts_at' => 'start date',
    ];
}
Enter fullscreen mode Exit fullscreen mode

Normalizing input with prepareForValidation()

Super useful for cleaning data before rules run.

protected function prepareForValidation(): void
{
    $this->merge([
        'name' => trim((string) $this->input('name')),
    ]);
}
Enter fullscreen mode Exit fullscreen mode

Common use cases:

  • trimming strings,
  • converting “on” to boolean,
  • mapping legacy keys,
  • defaulting missing values.

Advanced pattern: shared rules for Store/Update

Often your update rules differ slightly (e.g. unique constraints).

// app/Http/Requests/UpdateProjectRequest.php
use Illuminate\Validation\Rule;

public function rules(): array
{
    $projectId = $this->route('project')?->id;

    return [
        'name' => [
            'required', 'string', 'max:120',
            Rule::unique('projects', 'name')->ignore($projectId),
        ],
        'status' => ['required', Rule::in(['draft', 'active', 'archived'])],
    ];
}
Enter fullscreen mode Exit fullscreen mode

How Form Requests fit with Policies

A clean setup is:

  • Policy → resource-level permissions (“can update THIS project?”)
  • Form Request → input validation (+ optional action-level checks)

Example:

  • Controller calls $this->authorize('update', $project) (Policy)
  • Form Request validates fields and shapes the input

This separation keeps your authorization logic explicit and reusable.


Testing validation quickly

Even a few tests can prevent regressions.

public function test_project_name_is_required(): void
{
    $response = $this->post('/projects', [
        'name' => '',
        'status' => 'draft',
    ]);

    $response->assertSessionHasErrors(['name']);
}
Enter fullscreen mode Exit fullscreen mode

Copy/paste checklist

  • [ ] Create a Form Request per action (StoreXRequest, UpdateXRequest)
  • [ ] Keep controllers thin: $request->validated()
  • [ ] Normalize input in prepareForValidation() when needed
  • [ ] Use Policies for resource permissions; optionally call them in authorize()
  • [ ] Add 3–5 tests for critical validation rules

Wrap-up

Laravel Form Requests are a simple habit that keeps a project clean as it grows:

  • controllers stay readable,
  • validation rules stay consistent,
  • authorization can be clearer,
  • and refactors become safer.

Top comments (0)