Originally published on laravel.learnio.dev — part of the Laravel best practices course. View this lesson on the original site.

Validate input before it reaches your business logic

Validation is your first safety boundary. It protects controllers and services from malformed or incomplete payloads.

Start with request validation in controllers

For simple actions, validate inline:

public function store(Request $request)
{
    $data = $request->validate([
        'name' => ['required', 'string', 'max:255'],
        'email' => ['required', 'email'],
        'password' => ['required', 'min:8'],
    ]);

    // Use $data, not raw input
}

If validation fails, Laravel returns a proper error response automatically.

Move complex rules into form requests

Generate one:

php artisan make:request StoreUserRequest

Controller:

public function store(StoreUserRequest $request, CreateUserService $service)
{
    $service->handle($request->validated());

    return to_route('users.index');
}

This keeps controllers focused on orchestration.

Example request class app/Http/Requests/StoreUserRequest.php:

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

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

    public function rules(): array
    {
        return [
            'name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'email'],
            'password' => ['required', 'string', 'min:8'],
        ];
    }
}

Work with validated subsets

Sometimes you only need part of validated input:

$safe = $request->safe()->only(['name', 'email']);
$withoutMeta = $request->safe()->except(['debug_flag']);

This helps avoid passing unnecessary fields deeper into your app.

Bad way vs better way

Bad way (trust and pass everything):

public function store(Request $request, CreateUserService $service)
{
    $service->handle($request->all());
}

Better way (validate first, then pass trusted payload):

public function store(StoreUserRequest $request, CreateUserService $service)
{
    $service->handle($request->validated());
}

Why better:

  • less risk of unexpected fields reaching business logic,
  • clearer data contract between controller and service,
  • easier tests and safer refactors.

Practical rules for this chapter stage

  • validate at the HTTP boundary,
  • use form requests once rules grow,
  • pass validated arrays, not raw request bags,
  • keep business rules outside the controller method.

Validation flow

flowchart LR
  A[Form or API payload] --> B[Validation rules]
  B --> C[Validated data]
  C --> D[Service or domain action]
  D --> E[Redirect or JSON response]

Next, you will use this same validation baseline with route model binding to simplify lookup and not-found behaviour.

From Laravel best practices — Routing, Controllers & Request Flow