Rafhael Marsigli Logo

Why I Dropped Laravel Form Requests and API Resources in Favor of Data + Value Objects

4 min read
Why I Dropped Laravel Form Requests and API Resources in Favor of Data + Value Objects

Spoiler: it wasn’t because Laravel is bad. It was because I became more demanding.

For many years, I was 100% team native Laravel.

I used Form Requests religiously for validation. I used API Resources to transform responses. I defended these tools in projects, code reviews, and technical discussions. And honestly, they served me very well.

But as my projects grew — in complexity, longevity, and responsibility — I started to feel that something was… out of place.

Nothing was exactly wrong. But everything felt too scattered.

This post is about:

  • the real pains I experienced

  • the process of questioning old decisions

  • why I migrated to Spatie Laravel Data + Value Objects

  • and the advantages and disadvantages I consciously accepted

No dogma. No hype. Just engineering.

The discomfort started small (as it always does)

It all began with that classic feeling:

“Where exactly does this business rule live?”

Let’s look at a real example:

class UserResource extends JsonResource
{
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'email' => $this->email,
            'status' => $this->is_active ? 'active' : 'inactive',
        ];
    }
}

Controller in the middle, models below, services somewhere else.

Does it work? It does — very well, actually.

But notice:

  • Validation lives in one place

  • Transformation lives in another

  • Typing… lives nowhere

  • Conversion logic (is_activestatus) is hidden inside the Resource

Nothing breaks. But everything fragments.

The problem isn’t Laravel, it’s implicit coupling

What started to bother me was realizing that:

  • Form Requests validate, but don’t represent data

  • API Resources transform, but don’t guarantee contracts

  • The real shape of the data only exists… in your head

And that’s where the symptoms begin:

  • Rule duplication

  • Tests more verbose than they should be

  • Frontend relying on “tribal knowledge”

  • Refactors that feel scary

That’s when I started looking more seriously at real DTOs.

Not arrays with PHPDoc.

But actual Objects.

Enter the stage: Spatie Laravel Data

When I started using Spatie Data, the difference was immediate.

use Spatie\LaravelData\Data;

class UserData extends Data
{
    public function __construct(
        public readonly string $name,
        public readonly string $email,
        public readonly bool $isActive,
    ) {}
}

Now:

  • The shape

  • The validation

  • The contract

live together. No magic.

Goodbye API Resources (with affection)

This was the most emotionally painful part 😅. API Resources are great. But they encourage something dangerous:

late transformation

With Data Objects:

class UserData extends Data
{
    public function __construct(
        public readonly int $id,
        public readonly string $name,
        public readonly string $email,
        public readonly UserStatus $status,
    ) {}
}

The transformation happens earlier.

The controller only orchestrates:

return UserData::from($user);

No hidden logic.

No magic ifs inside toArray().

Value Objects are what completes the reasoning

It was impossible to stop at Data alone.

If I was already typing everything… why still use string for things that aren’t really strings? Why not use a Value Object for critical validations?

final class Email
{
    public function __construct(public readonly string $value)
    {
        if (! filter_var($value, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException('Invalid email');
        }
    }
}

Now:

  • An email is always valid

  • Invalid state doesn’t exist

  • The rule never repeats itself

This is liberating.

What was the real day-to-day impact?

After the migration, the gains were clear:

Advantages

  • Explicit contracts

  • Much safer refactors

  • Fewer defensive tests

  • Better integration with TypeScript

  • IDE working in my favor

Disadvantages (yes, they exist)

  • More classes

  • Learning curve for the team

  • Slightly more verbose initial code

  • Less immediate “convenience”

  • Overengineering (a lot, way too much) for small projects

And I’m 100% OK with that.

I prefer:

clarity now over pain later

It’s not about Spatie. It’s about maturity

This post is not an attack on Laravel.

It’s an acknowledgment that:

  • Native tools solve 80%

  • Large projects live in the other 20%

Spatie Data + Value Objects gave me something I didn’t have before:

structural confidence

Today, when I refactor, I don’t hope nothing breaks.

The compiler tells me.

And after years of fighting silent bugs… that’s priceless.

Is it mandatory?

If you’re comfortable with Form Requests and API Resources, great. That’s totally fine.

But if you’ve started feeling that subtle discomfort… maybe it’s time to listen. Sometimes change hurts a bit. But it’s worth it.

Share with those you love