Unified Data Management in Laravel with Laravel-Data

Overview of the Data Definition Problem

Modern web applications often suffer from a "multiple definition" problem. When building a feature, you typically define the same set of attributes in a

form request for validation, again in an API resource for output, and a third time as a
TypeScript
interface for your frontend. This duplication isn't just tedious; it's a breeding ground for bugs. If you add a middle_name field to your database but forget to update the API resource, your frontend breaks.
Laravel-Data
, a package by
Spatie
, solves this by providing a single source of truth: the Data Object. This object handles validation, transformation, and type generation in one elegant class.

Prerequisites and Toolkit

To follow this guide, you should be comfortable with

8.x and the
Laravel
framework. You should understand the basics of
Inertia.js
if you plan on using the frontend features, and have a working knowledge of
TypeScript
for the automated type generation components. This tutorial assumes you have a
Laravel
project where you're tired of writing repetitive boilerplate for requests and resources.

Key Libraries & Tools

  • Laravel-Data
    : The core package that creates powerful data objects to replace form requests and API resources.
  • TypeScript
    Transformer: A built-in feature of the package that scans your
    PHP
    classes and generates matching
    TypeScript
    definitions.
  • Inertia.js
    : Frequently used alongside this package to bridge the gap between backend data and frontend
    React
    or
    Vue
    components.

Code Walkthrough: Implementing a Data Object

Let's replace the standard

ceremony with a single
Laravel-Data
object. Instead of creating a StoreContactRequest and a ContactResource, we create a ContactData class.

namespace App\Data;

use Spatie\LaravelData\Data;
use Spatie\LaravelData\Attributes\Validation\Email;

class ContactData extends Data
{
    public function __construct(
        public string $name,
        #[Email]
        public string $email,
        public ?string $address,
    ) {}
}

By extending the Data class, this object now performs multiple roles. When used in a controller, it automatically validates incoming request data based on the property types. For example, the string $name property implies a required|string validation rule. If you want more specific constraints, use attributes like #[Email].

In your controller, you can swap out the standard request and resource calls:

public function update(ContactData $contactData, Contact $contact)
{
    $contact->update($contactData->toArray());
    return back();
}

public function show(Contact $contact)
{
    return ContactData::from($contact);
}

The from() method is a "smart" factory. It accepts a model, an array, or a request and maps the attributes automatically. This eliminates the manual mapping usually found in

.

Automated TypeScript Integration

One of the most powerful features of

is the ability to keep your frontend types in sync. By running a simple
Laravel
command, the package scans your data objects and generates a
TypeScript
file.

php artisan typescript:transform

This generates interfaces that match your

logic exactly. If a property is nullable in
PHP
(?string), it becomes optional or nullable in
TypeScript
. This bridge ensures that your
React
components have full auto-completion and type safety, preventing "undefined" errors at runtime.

Advanced Features: Nesting and Route Actions

Real-world data is rarely flat.

supports nested data objects seamlessly. If a Project has many Guests, you can define a ProjectData class that contains a collection of GuestData objects. The
TypeScript
transformer will respect this hierarchy, generating nested interfaces.

In the upcoming Version 4, the package introduces a Route Action Helper. This tool scans your

routes and generates
TypeScript
helpers. This allows you to call routes in your frontend using the controller name and method with full auto-completion for route parameters. You no longer need to hardcode URLs or guess which parameters a specific endpoint requires.

Tips & Gotchas

While

is powerful, don't feel obligated to use it for every single interaction. Standard
Laravel
form requests are still excellent for simple validation logic in smaller projects. Use
Laravel-Data
when you find yourself repeating the same attribute list across three or more files.

Best Practice: Always use the from() method rather than manual instantiation. It handles the heavy lifting of converting models and multi-dimensional arrays into the correct object format. If you need to hide sensitive data like addresses for certain users, use the except() or only() methods on the data object to filter the output dynamically.

5 min read