How I use Laravel, React and Herd

I have worked with the MERN stack and with Laravel on its own. Those projects helped me understand what I like about each side of an application: a clear backend structure, and an interface that can respond without making every interaction feel like a new page.

Laravel and React have been a useful combination for that. I have used them in experiments with the PokéAPI and in Kanban-style task managers with boards, lists, cards and subtasks. Forms, relationships and the movement of data between screens come up repeatedly in that work.

This is the workflow I use for those projects. The setup uses Laravel Breeze; other starter kits have their own installation process.

Starting locally with Herd

I use macOS as well as KDE on Linux. On my Mac, Herd made it straightforward to start working on a Laravel project with a local .test domain. That removed some setup work before I could get to the application itself.

I set up Breeze with these commands:

composer global require laravel/installer
laravel new my-laravel-react-app
cd my-laravel-react-app
composer require laravel/breeze --dev
php artisan breeze:install react
npm install
npm run dev

Those commands are specific to this stack. Laravel’s Breeze documentation is the reference for this approach; I would check the documentation for the chosen Laravel version before starting a new project.

Keeping form responsibilities clear

Forms were central to both kinds of application. The Pokémon experiment needed search and filtering. The task managers needed ways to add and edit tasks, lists and subtasks.

I use React state for the values being edited and Laravel Form Requests for server-side validation. A simple project request can express its rules like this:

public function rules()
{
    return [
        'title' => 'required|string|max:255',
        'description' => 'nullable|string',
    ];
}

Those rules describe the expected data. In the React form, the title and description are controlled inputs: changing a field updates its state, and submitting passes those values to the submit handler.

I find that separation easier to follow than allowing the interface and the backend to each make their own assumptions. Laravel’s Form Request documentation covers the validation and authorisation responsibilities of the request class. The small method above is only the rules portion.

Writing the database structure down

A project starts with fairly ordinary fields. Here is a small migration for a projects table:

Schema::create('projects', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->text('description')->nullable();
    $table->date('deadline')->nullable();
    $table->string('status')->default('pending');
    $table->integer('priority')->default(0);
    $table->timestamps();
});

I have used AI to help draft migrations when there is a long list of fields. Describing the fields and their relationships is often quicker than repeatedly typing the surrounding syntax. I have used the same approach for seeders, relationship definitions and initial validation rules.

In a nested Kanban project, that helped me get through migrations for boards, lists, cards and subtasks. The point was to leave more time for the application’s behaviour and interface. The decisions about what the data means still belong in the development work.

Passing data through Inertia

With the Breeze React stack, I used Inertia to pass data from a Laravel controller to a React page. A small controller method looks like this:

public function index()
{
    $projects = Project::all();
    return Inertia::render('Projects/Index', [
        'projects' => $projects
    ]);
}

The corresponding React component receives a projects prop and maps over it to display the project titles. This example shows the connection between the two layers. It is not a complete project-access policy: the data a real application returns needs to match that application’s permissions.

I liked being able to follow that route in one codebase, from the Laravel controller through to the component rendering the result. For these projects, I did not need a separate API simply to display the data on the next screen.

Following a field all the way through

Adding a due date to a task made the same connection visible on a smaller scale. I needed to account for the database field, the model, the data passed to React and the input shown in the form.

  • Add the field to the database structure.
  • Update the model where it needs to accept the field.
  • Pass the value to the React view.
  • Add the date input to the form.

That is the part of this workflow I keep coming back to. It gives me a route I can trace when something is missing or behaving unexpectedly. Whether the application is a small Pokémon experiment or a task manager with nested data, I want to understand how a value gets from the form to storage and back to the screen.

More about me