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_active→status) 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.