Effortless Laravel & Inertia Data and Type Sync: DTO Magic

Effortless Laravel & Inertia Data and Type Sync: DTO Magic

Laravel Laravel
A

Ali Alizadeh

Fri Mar 14 2025

2 min

Table of Contents

  1. Why ?
  2. Solution
  3. Configuring the Backend
  4. Using the Data
  5. Configuring the Frontend
  6. Tips and Tricks

Why ?

Let's face it: backend as the source of truth, frontend as the eager student. We've all been there, reinventing type wheels like we're running a medieval blacksmith shop for data structures.

After a few project-induced brain wrinkles, I believe I've stumbled upon a solution that's less "syncing" and more "harmonious data ballet."

Solution

Enter spatie/laravel-data not just a package, but a full-blown data orchestra. It's got more features than a Swiss Army knife, and we're primarily here for the type generation goodness. Our frontend will simply tap its feet to the backend's type-generated rhythm.

Configuring the Backend

First, let's get the band together:

composer require spatie/laravel-data
php artisan vendor:publish --provider="Spatie\LaravelData\LaravelDataServiceProvider" --tag="data-config"

Next we need an enum transformer called spatie/enums which transforms our enums to typescript enums. Only if we want to use transform_to_native_enums in the transformer config Which we do.

composer require spatie/enum

Next we need typescript transformer called spatie/laravel-typescript-transformer to transform our class data to typescript types

composer require spatie/laravel-typescript-transformer
php artisan vendor:publish --tag=typescript-transformer-config

I usually change two things from the default config:

use Spatie\TypeScriptTransformer\Formatters\PrettierFormatter;

[
    'output_file' => resource_path('js/@types/generated.d.ts'),
    'formatter' => PrettierFormatter::class,
    'transform_to_native_enums' => true,
]

Notice we've set the transform_to_native_enums to true that's why we need to Install spatie/enum

Now, let's compose our PostData masterpiece:

namespace App\Enums\Post;

enum PostStatus: string
{
    case Published = 'PUBLISHED';
    case Draft = 'DRAFT';
}
namespace App\Data\Post;

use Spatie\LaravelData\Data;
use Spatie\TypeScriptTransformer\Attributes\TypeScript;
use App\Enums\Post\PostStatus;

#[TypeScript]
class PostData extends Data
{
    public function __construct(
        public int $id,
        public string $title,
        public string $content,
        public PostStatus $status,
    ) {}
}

That #[TypeScript] attribute? It's telling the transformer to work its magic.

And conduct the transformation:

php artisan typescript:transform

Using the Data

Let's put PostData on stage:

namespace App\Http\Controllers;

use App\Models\Post;
use App\Data\Post\PostData;

class PostController extends Controller
{
    public function index(): \Inertia\Response
    {
        $posts = Post::all();

        return Inertia::render('posts/Index', [
            'posts' => PostData::collect($posts),
        ]);
    }

    public function show(Post $post): \Inertia\Response
    {
        return Inertia::render('posts/Show', [
            'post' => PostData::from($post),
        ]);
    }
}

Configuring the Frontend

The best part? Minimal effort. Just embrace the types:

type TPage = {
  posts: App.Data.Post.PostData[];
};

Tips and Tricks

Generating types manually? That's like tuning a guitar between every chord. Let's automate with a pre-commit hook using husky:

npx husky init && npm i

And in .husky/pre-commit:

php artisan typescript:transform

For extra credit, let's add type checking in package.json:

"ts:check": "tsc --project tsconfig.json --noEmit"

and add this command to the pre-commit file

npm run ts:check

So in the end we have:

php artisan typescript:transform
npm run ts:check

Two birds, one stone – type generation and type safety, all before your code even leaves the nest. Now that's what I call a productive pre-commit party!

More Blogs

Shoes

Simplifying Laravel & Inertia React Forms: A Custom Wrapper

Enhance your Laravel and Inertia React form development with our custom wrapper, eliminating repetitive code and streamlining form management.

Laravel LaravelReact React