Gutting the new Livewire starter Kit in Laravel 12

Published by Andrés López

Gutting the new Livewire starter Kit in Laravel 12

Every time something new is implemented in Laravel, I enjoy dissecting its source code. You can learn a lot about programming, Laravel, and even PHP by deeply reviewing what other developers create, especially those who contribute to the Laravel framework.

Let’s take a quick look, and to do so, we need to install the starter kit:

laravel new livewire-app

Livewire starter kit installation

Don’t forget to run npm install and npm run dev.

I’ll skip the obvious functionality of the starter kit. We already know how Livewire works, authentication in Laravel, and in general, how to build this logic. I’ll focus on the curious things I saw and some practices you could implement in other sections of your application.

Forced to Use Volt?

At first glance, everything is installed at the application level. There are no new dependencies (as was the case with Laravel UI) beyond Livewire. We can see Volt even though we opted out of it during installation. Curious:

// composer.json
"require": {
        "php": "^8.2",
        "laravel/framework": "^12.0",
        "laravel/tinker": "^2.10.1",
        "livewire/flux": "^2.0",
        "livewire/volt": "^1.6.7"
    },
"require-dev": {
    "fakerphp/faker": "^1.23",
    "laravel/pail": "^1.2.2",
    "laravel/pint": "^1.18",
    "laravel/sail": "^1.41",
    "mockery/mockery": "^1.6",
    "nunomaduro/collision": "^8.6",
    "pestphp/pest": "^3.7",
    "pestphp/pest-plugin-laravel": "^3.1"
},

Laravel Routing

One curious thing I noticed is that this starter kit separates authentication routes.

routes/
    auth.php
    web.php
    console.php

I’m not sure why, but they add this new file to the routes using a require. I’m not saying it’s bad, but you can always modify the bootstrap/app.php file to add new routers. Here’s how you can do it without the require:

// boostrap/app.php
<?php

use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: [
            __DIR__.'/../routes/web.php',
            __DIR__.'/../routes/auth.php' // add this line        ],
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {
        //
    })
    ->withExceptions(function (Exceptions $exceptions) {
        //
    })->create();

The Magic Method __invoke

There’s an interesting implementation for creating actions in PHP, thanks to the __invoke method. Imagine we have the following class:

<?php

namespace App\Livewire\Actions;

use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session;

class Logout
{
    /**
     * Log the current user out of the application.
     */
    public function __invoke()
    {
        Auth::guard('web')->logout();

        Session::invalidate();
        Session::regenerateToken();

        return redirect('/');
    }
}

You might wonder how we call it. Maybe like this...

use App\Livewire\Actions\Logout;

new Logout()->__invoke(); // we’re using PHP 8.4, this is totally normal

Nah, this looks horrible, right?

Well, in the new starter kit, we can see the use of resolving from Laravel’s dependency injection combined with the magic method __invoke:

// app/Livewire/Auth/VerifyEmail.php

public function logout(Logout $logout): void
{
    $logout(); // esta definición llama al método __invoke
    $this->redirect('/', navigate: true);
}

A very elegant way to use an actions pattern in your application.

Changing the Theme

Although this is an exclusive feature of Flux UI, I still find it very interesting to include it. In one of the Livewire components, app/Livewire/Settings/Appearance.php, there’s not even any implementation code. However, all the magic is in its view, specifically in the use of $flux.appearance, which is manipulated via AlpineJS, and this value is stored in the browser’s localStorage.

<div class="flex flex-col items-start">
    @include('partials.settings-heading')

    <x-settings.layout :heading="__('Appearance')" :subheading=" __('Update the appearance settings for your account')">
        <flux:radio.group x-data variant="segmented" x-model="$flux.appearance">
            <flux:radio value="light" icon="sun">{{ __('Light') }}</flux:radio>
            <flux:radio value="dark" icon="moon">{{ __('Dark') }}</flux:radio>
            <flux:radio value="system" icon="computer-desktop">{{ __('System') }}</flux:radio>
        </flux:radio.group>
    </x-settings.layout>
</div>

If you need to access it without using AlpineJS, you can do it like this:

console.log(window.Flux.appearance)
window.Flux.appearance = 'system'
window.Flux.appearance = 'dark'
window.Flux.appearance = 'light'

Handling Validations in Livewire

Inside the app/Livewire/Settings/Password.php file, we can find very interesting code. For example, field validation. Quick explanation: when we work with automatic validation in Laravel, what’s really happening is that internally, Laravel is throwing exceptions every time the validate method is called and the fields don’t meet the validation rules we defined. That’s why it feels so magical that the user automatically gets feedback with so little development.

And, as you might have guessed, if we handle exceptions, we can catch them in a try-catch block. That’s exactly what makes this code so interesting, as it leverages the functionality of PHP, Laravel, and Livewire:

  1. It uses a try-catch to catch exceptions of type Illuminate\Validation\ValidationException (PHP).
  2. Validates the fields using the validate function (Laravel).
  3. Clears the password fields for security (Livewire).
  4. Re-throws the previously caught exception (PHP).
  5. Displays validation error messages to the user (Laravel).
use Illuminate\Validation\ValidationException;

public function updatePassword(): void
{
    try {
        $validated = $this->validate([
            'current_password' => ['required', 'string', 'current_password'],
            'password' => ['required', 'string', PasswordRule::defaults(), 'confirmed'],
        ]);
    } catch (ValidationException $e) {
        $this->reset('current_password', 'password', 'password_confirmation');

        throw $e;
    }
}

Definitely a short but powerful implementation.

I invite you to try this starter kit and learn more about Livewire. Just remember that if you plan to use Livewire in a serious application, you’ll definitely need to use AlpineJS to create good interactions, response times, and user experiences worthy of a good developer.

Andrés López

Andrés López

Laravel lover, Vue enthusiast & writer of everything sounds interesting