DEV Community

Cover image for Laravel + HTMX = ❤️
Turcu Laurentiu
Turcu Laurentiu

Posted on • Updated on

Laravel + HTMX = ❤️

Enhancing Chirper: A Journey from Blade to a Modern Web App

In this article, we are going to explore how we can utilize HTMX alongside Laravel to modernize the Laravel Bootcamp Chirper blade project. As a primer, feel free to explore the live implementation at chirper.tlaurentiu.net to get a hands-on feel of what we'll be delving into.
If you don't wish to register, you can access it with my credentials (turculaurentiu91@gmail.com | password )
Our aim is to convert this project into a modern web app by integrating HTMX

Along the way, we'll delve into advanced techniques to introduce features like active search, infinite scrolling, and real-time updates for the page when new elements are created by other users.

why HTMX?

Because I love it! Since I landed on their website and understood the simplicity it can bring on a web developer life I can't stop thinking how almost everything we do on the web with React (or other frameworks 🙂) can be ridiculously easy done with plain old HTML enhanced with HTMX.

  • Yes, you can achieve the same with HTTP API & React (or other frameworks 🙂).
  • Yes, you can achieve the same with Inertia.js
  • Yes, you can achieve the same with Livewire

But this tutorial is about HTMX and how easy it is to use. Think of it as just another tool in your toolbox that you can use to replace or use alongside of the above mentioned technologies.

This article is not about why you should use it, but how you can use it. But the gist of it is that is dead simple, and it keeps your apps simple. And simple is good!

If you want read more about the why, you can take a look at this other article.

Getting started

In order to follow along, you need to have completed the Laravel Bootcamp Chirper app using the blade templates. If you are familiar with Laravel and Blade and prefer not to go through that tutorial, you can clone my version of the solution (which replicates all the code from the bootcamp) from here: GitHub Repository. Be sure to check out the the-beginning branch, as the main branch contains the final solution.

The next step is to install HTMX. We can take two routes here:

  1. Install it via npm, bundle it with Vite (a build tool that facilitates faster development).
  2. Load the script via CDN in the header of your template.

Usually, the first option is preferable as it allows HTMX to be served by your infrastructure, enabling you to take advantage of HTTP/2 multiplexed streams. However, for the sake of simplicity in this tutorial, we will add it directly in the templates.

<!-- resources/views/layouts/app.blade.php -->

      <script src="https://unpkg.com/htmx.org@1.9.6" integrity="sha384-FhXw7b6AlE/jyjlZH5iHa/tTe9EpJ1Y55RjcgPbjeWMskSxZt1v9qkxLJWNJaGni" crossorigin="anonymous"></script>
</head>
Enter fullscreen mode Exit fullscreen mode

So, we just added the script in the head from unpkg. That's all, we can now go and use it.

Boosting the navigation

If you open the application, login, and navigate between the Dashboard and the Chirps tab, you will notice that the whole page is reloaded with each navigation. This is where frameworks like React shine by enabling seamless navigation without page reloads. Now, let's see how we can achieve similar seamless navigation with HTMX, by taking advantage of the hx-boost attribute:

So let's see how we can achieve that with HTMX, by taking advantage of the hx-boost attribute:

<!-- resources/views/layouts/navigation.blade.php -->
<!-- Navigation Links -->  
<div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex" hx-boost="true">  
    <x-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')">  
        {{ __('Dashboard') }}  
    </x-nav-link>  
    <x-nav-link :href="route('chirps.index')" :active="request()->routeIs('chirps.index')">  
        {{ __('Chirps') }}  
    </x-nav-link>  
</div>
Enter fullscreen mode Exit fullscreen mode

We added the hx-boost="true" to the parent div of the navigation links. What HTMX will do behind the scene is:

  1. It will install an onClick event for the links.
  2. It will prevent the default browser navigation.
  3. It will do an AJAX request to the URL specified in the href attribute.
  4. It will fetch the HTML document.
  5. It will replace the <body> of the document with the content received in the request.
  6. It will update the navigation URL to match the href attribute of the clicked anchior.

Now, when you click on the navigation links, the whole page will no longer reload, demonstrating the power and simplicity of HTMX.

HTMX attributes are inheritable, meaning that you can inherit attributes from ancestor elements. Note how we added the hx-boost attr. on the parent div of the navigation links. We can, in fact, we will move the attribute on the wrapper div of the whole page.

So we remove it from resources/views/layouts/navigation.blade.php and add it on the app layout

<!-- resources/views/layouts/app.blade.php -->
<body class="font-sans antialiased">  
    <div class="min-h-screen bg-gray-100" hx-boost="true">
    <!-- rest of the template -->

Enter fullscreen mode Exit fullscreen mode

And so, we added the boost to all logged-in users. Now, whatever action the user takes—creating a chirp, editing, deleting, or interacting with any other forms—they will not trigger a whole page reload.

HOMEWORK: add the boost to the guest users, so the auth forms has the same boost.

Checkmate React, our job here is done. With one <script> tag and one HTML attribute we replaced react, react-router, redux, Axios and a couple of other js dependencies that you would install just because you can.

CSS Transitions

You might notice that the navigation links have a CSS transition property, but it only works when you hover to display the underline. When you click, the underline just pops into existence, which can be jarring. Let's smooth that out!

<!-- resources/views/layouts/navigation.blade.php -->  
<!-- Navigation Links -->  
<div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">  
    <x-nav-link id="dashboard-link" :href="route('dashboard')" :active="request()->routeIs('dashboard')">  
        {{ __('Dashboard') }}  
    </x-nav-link>  
    <x-nav-link id="chirps-link" :href="route('chirps.index')" :active="request()->routeIs('chirps.index')">  
        {{ __('Chirps') }}  
    </x-nav-link>  
</div>
    <!-- rest of the template -->
Enter fullscreen mode Exit fullscreen mode

Yes, all we did was to add some ids on the links.

To apply a CSS transition to swapped content, it's essential to keep its IDs consistent across requests. Under the hood, HTMX will:

  1. Swap the content,
  2. Match the elements by ID,
  3. Apply the existing properties (CSS classes in our case) to the new elements,
  4. And after a brief pause of a few milliseconds (20), apply the new properties to these elements.

This sequence allows the CSS transitions to function smoothly, providing a visual cue to users as the active navigation link changes. Now, when you click on the navigation links, the underline will transition smoothly instead of just popping into existence, enhancing the user experience with a more polished, professional look.

By making this simple adjustment, we've improved the visual feedback to users as they navigate through the application, showcasing another aspect of the user-friendly enhancements HTMX can bring to a Laravel project.

For more detailed information on how CSS transitions are handled with HTMX, feel free to check out the HTMX documentation page on CSS transitions.

Enhancing the edit action

Imagine your Product Owner comes around and mentions disliking the redirection to another page when editing a chirp. He prefer that upon clicking the edit button, the chirp morphs into a form. On saving, it reverts back to a regular chirp displaying the updated content. Let’s delve into how we can achieve this.

The edit button

The goal is to convert the chirp into a form upon clicking the edit button. To do this, we'll create a Blade component that renders a chirp in edit mode.

<!-- resources/views/chirps/edit.blade.php -->

@props(['chirp'])  

<div hx-target="this" hx-swap="outerHTML">  
    <form 
        class="p-6 flex flex-col space-x-2" 
        hx-patch="{{ route('chirps.update', $chirp) }}"
     >  
        @csrf  
        <textarea  
            name="message"  
            class="block w-full border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm"  
        >
            {{ old('message', $chirp->message) }}
        </textarea>  
        <x-input-error :messages="$errors->get('message')" class="mt-2" />  
        <div class="mt-4 space-x-2">  
            <x-primary-button>{{ __('Save') }}</x-primary-button>  
            <a class="cursor-pointer" 
            hx-get="{{ route('chirps.show', $chirp) }}"  
            >
                {{ __('Cancel') }}
            </a>  
        </div>    
    </form>
</div>
Enter fullscreen mode Exit fullscreen mode

Here, we've created a new Blade component that renders the chirp form inline. The hx-target and hx-swap attributes on the wrapper div instruct HTMX to target this div (itself) and replace its outerHTML with the response contents for all requests within this div, thanks to HTMX's attribute inheritance.

On the <form> element and the cancel link, we've specified the request methods to be PATCH and GET respectively.

Updating the Controller

Now, let’s modify the update method in our controller to respond with the partial view if it's an HTMX request, maintaining the normal browser functionality when JavaScript is disabled for a progressively enhanced website.

// app/Http/Controllers/ChirpController.php

 public function update(Request $request, Chirp $chirp): RedirectResponse|Response  
{  
    $this->authorize('update', $chirp);  

    $validated = $request->validate([  
        'message' => 'required|string|max:255',  
    ]);  

    $chirp->update($validated);  

    if($request->header('HX-Request')) {  
        return response()->view('components.chirps.single', [  
            'chirp' => $chirp,  
        ]);  
    }  

    return redirect(route('chirps.index'));  
}
Enter fullscreen mode Exit fullscreen mode

The Single Chirp Component

Now, let's discuss the single chirp component which we've extracted for reusability.

<!-- resources/views/components/chirps/single.blade.php -->

@props(['chirp'])  

<div class="p-6 flex space-x-2 chirp">  
    <svg 
        xmlns="http://www.w3.org/2000/svg" 
        class="h-6 w-6 text-gray-600 -scale-x-100" 
        fill="none" viewBox="0 0 24 24" 
        stroke="currentColor" 
        stroke-width="2">  
        <path 
            stroke-linecap="round" 
            stroke-linejoin="round" 
            d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />  
    </svg>    
    <div class="flex-1">  
        <div class="flex justify-between items-center">  
            <div>                
                <span class="text-gray-800">{{ $chirp->user->name }}</span>  
                <small class="ml-2 text-sm text-gray-600">
                    {{ $chirp->created_at->format('j M Y, g:i a') }}
                </small>  
                @unless ($chirp->created_at->eq($chirp->updated_at))  
                    <small class="text-sm text-gray-600"> 
                        &middot; {{ __('edited') }}
                    </small>  
                @endunless  
            </div>  
            @if ($chirp->user->is(auth()->user()))  
                <x-dropdown>  
                    <x-slot name="trigger">  
                        <button>                            
                            <svg 
                                xmlns="http://www.w3.org/2000/svg" 
                                class="h-4 w-4 text-gray-400" 
                                viewBox="0 0 20 20" 
                                fill="currentColor">  
                                <path d="M6 10a2 2 0 11-4 0 2 2 0 014 0zM12 10a2 2 0 11-4 0 2 2 0 014 0zM16 12a2 2 0 100-4 2 2 0 000 4z" />  
                            </svg>                        
                        </button>                   
                    </x-slot>                    
                    <x-slot name="content">  
                        <x-dropdown-link                          
                            :href="route('chirps.edit', $chirp)"  
                            hx-get="{{ route('chirps.edit', $chirp) }}"  
                            hx-target="closest .chirp"  
                            hx-swap="outerHTML"  
                        >  
                            {{ __('Edit') }}  
                        </x-dropdown-link>  
                        <form 
                            method="POST" 
                            action="{{ route('chirps.destroy', $chirp) }}">  
                            @csrf  
                            @method('delete')  
                            <x-dropdown-link 
                                :href="route('chirps.destroy', $chirp)" onclick="event.preventDefault(); this.closest('form').submit();">  
                                {{ __('Delete') }}  
                            </x-dropdown-link>  
                        </form>                    
                    </x-slot>                
                </x-dropdown>            
            @endif  
        </div>  
        <p class="mt-4 text-lg text-gray-900">{{ $chirp->message }}</p>  
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode

We've moved the chirp rendering code to a reusable Blade component. The HTMX attributes on the edit button ensure that a GET request is made to the edit endpoint, replacing the closest parent div with class chirp with the response markup.

Edit Chirp Partial/Component

Ensure that HTMX requests to the edit chirp endpoint receive only the edit chirp component, while other requests receive the whole page, aligning with the "progressive enhancement" pattern.

// app/Http/Controllers/ChirpController.php

public function edit(Request $request, Chirp $chirp): Response  
{  
    $this->authorize('update', $chirp);  

    if($request->header('HX-Request')) {  
        return response()->view('components.chirps.edit', [  
            'chirp' => $chirp,  
        ]);  
    }  

    return response()->view('chirps.edit', [  
        'chirp' => $chirp,  
    ]);  
}
Enter fullscreen mode Exit fullscreen mode

Now, update the index layout to utilize the single chirp component and implement the view endpoint of the single chirp.

<!-- resources/views/chirps/index.blade.php -->

<div class="mt-6 bg-white shadow-sm rounded-lg divide-y">  
    @foreach ($chirps as $chirp)  
        <x-chirps.single :chirp="$chirp" />  
    @endforeach  
</div>
Enter fullscreen mode Exit fullscreen mode
public function show(Chirp $chirp): Response  
{  
    return response()->view('components.chirps.single', [  
        'chirp' => $chirp,  
    ]);  
}
Enter fullscreen mode Exit fullscreen mode

Don't forget to add the new endpoint to routes.php.

Review

Let's review the flow:

  1. Clicking the edit button triggers a GET request to chirps/{chirp}/edit, replacing the parent div with class chirp with the response markup from the components/chirps/edit partial template.
  2. Clicking the cancel link on the form triggers a GET request to chirps/{chirp}, replacing the wrapper div element with the content of the response, i.e., the partial markup of the single chirp element.
  3. Submitting the edit form triggers a PATCH request to chirps/{chirp} endpoint, updating the chirp and replacing the wrapper div element with the markup of the single chirp, the edited one.

With the HX-Request header, we've maintained normal browser functionality in case JavaScript is disabled, embodying the principle of progressive enhancement. Our application remains server-side rendered, fast, and simple, with a significantly enhanced user experience.

PS: Note that the validation isn't working currently; we'll address this later in the tutorial.

Through these steps, we've seamlessly integrated HTMX to enhance the edit action in our Laravel application, providing a more intuitive and interactive user experience without the need for complex front-end frameworks.

Enhancing the create action.

The create action can be enhanced by making two key changes:

  • Modifying the store action to return the single chirp during an HTMX request.
  • Injecting the single chirp markup at the beginning of the chirps list.

Let’s delve into these steps:

Updating the Store Action

Update the store method in ChirpController to return the partial view of a single chirp when an HTMX request is detected:

// app/Http/Controllers/ChirpController.php

public function store(Request $request): RedirectResponse|Response  
{  
    $validated = $request->validate([  
        'message' => 'required|string|max:255',  
    ]);  

    $chirp = $request->user()->chirps()->create($validated);  

    if($request->header('HX-Request')) {  
        return response()->view('components.chirps.single', [  
            'chirp' => $chirp,  
        ]);  
    }  

    return redirect()->route('chirps.index');  
}
Enter fullscreen mode Exit fullscreen mode

Modifying the Index Template

In the chirps.index template, update the form element by adding HTMX attributes, instructing it to insert the response at the top of the chirps list (afterbegin). Also, add an ID to the chirps list div for targeting:

<form  
    method="POST"  
    action="{{ route('chirps.store') }}"  
    hx-post="{{ route('chirps.store') }}"  
    hx-target="#chirps"  
    hx-swap="afterbegin"  >

<!-- rest of the template -->

<div id="chirps" class="mt-6 bg-white shadow-sm rounded-lg divide-y">
Enter fullscreen mode Exit fullscreen mode

Handling Form Reset

You may notice that the textarea content remains unchanged even after a successful request. To enhance user experience, let's reset the form upon a successful submission using the hx-on attribute:

<form  
    method="POST"  
    action="{{ route('chirps.store') }}"  
    hx-post="{{ route('chirps.store') }}"  
    hx-target="#chirps"  
    hx-swap="afterbegin"  
    hx-on="htmx:afterRequest: if(event.detail.successful) this.reset();"  
>
Enter fullscreen mode Exit fullscreen mode

Here, we've used the hx-on attribute to bind a script to the htmx:afterRequest event, which HTMX triggers after each request. We check whether the request was successful using event.detail.successful, and if so, we reset the form using this.reset().

And there you have it! We’ve significantly enhanced the create method.

Note: The validation is not yet functioning correctly, but we'll address this issue later in the tutorial.

These steps demonstrate how we've seamlessly integrated HTMX to improve the create action in our Laravel application, providing a better user experience without adding complexity to the Front-end.

Handling Delete Action

The delete action is relatively straightforward. We can respond with an empty string for a successful delete HTMX request, instructing HTMX to remove the item from the DOM. For non-HTMX requests, the behavior remains unchanged.

Updating the Destroy Method

Modify the destroy method in ChirpController to return an empty string during an HTMX request, and to redirect to the chirps.index route otherwise.

public function destroy(Request $request, Chirp $chirp): RedirectResponse|string  
{  
    $this->authorize('delete', $chirp);  

    $chirp->delete();  

    if($request->header('HX-Request')) {  
        return '';  
    }  

    return redirect(route('chirps.index'));  
}
Enter fullscreen mode Exit fullscreen mode

Modifying the Dropdown-Link Component

The current implementation in Laravel Bootcamp Chirper has a <x-dropdown-link> component that always renders a link. To ensure it works even with JavaScript disabled, modify it to render a button instead, like so:

<!-- resources/views/components/dropdown-link.blade.php -->

@props(['component' => 'link'])  

@if ($component === 'link')  
    <a {{ $attributes->merge(['class' => 'block w-full px-4 py-2 text-left text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out']) }}>{{ $slot }}</a>  
@else  
    <button {{ $attributes->merge(['class' => 'block w-full px-4 py-2 text-left text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out']) }}>{{ $slot }}</button>  
@endif
Enter fullscreen mode Exit fullscreen mode

here I shamelessly copied the classes around and not used a variable to be more DRY

Adjusting the Single Chirp Component

Now, update the single chirp component to utilize the modified <x-dropdown-link> and adjust the form to execute a delete request, removing the chirp from the DOM upon request completion.


<!-- resources/views/components/chirps/single.blade.php -->

<form method="POST"  
      action="{{ route('chirps.destroy', $chirp) }}"  
      hx-delete="{{route('chirps.destroy', $chirp)}}"  
      hx-target="closest .chirp"  
      hx-swap="delete"  
>  
    @csrf  
    @method('delete')  
    <x-dropdown-link  
        :component="'button'"  
        type="submit">  
        {{ __('Delete') }}  
    </x-dropdown-link>  
</form>
Enter fullscreen mode Exit fullscreen mode

Now, a submit button is rendered as a dropdown-link, and the form is adjusted to make a delete request, removing the chirp from the DOM upon request completion.

While it's noted that the dropdown link now leverages Alpine.js and may not function without JavaScript, addressing this falls outside the scope of this tutorial. The focus here is on showcasing the HTMX integration to handle delete actions seamlessly.

Implementing Infinite Scrolling

Let's first generate additional chirps using Laravel's factory and seeder functionality:

php artisan make:factory ChirpFactory --model Chirp
Enter fullscreen mode Exit fullscreen mode
<?php  

namespace Database\Factories;  

use App\Models\Chirp;  
use Illuminate\Database\Eloquent\Factories\Factory;  

/**  
 * @extends Factory<Chirp>  
 */  
class ChirpFactory extends Factory  
{  
    /**  
     * Define the model's default state.     
     *     
     * @return array<string, mixed>  
     */    
     public function definition(): array  
    {  
        $created_at = $this->faker->dateTimeBetween('-2 months');  

        return [  
            'message' => $this->faker->sentence,  
            'created_at' => /* somewhere in the past two months */ $created_at,  
            'updated_at' => $this->faker->boolean() ? $this->faker->dateTimeBetween($created_at) : null,  
        ];  
    }  
}
Enter fullscreen mode Exit fullscreen mode

// DatabaseSeeder.php

public function run(): void  
{  
    User::factory(20)->hasChirps(2000)->create();  
}
Enter fullscreen mode Exit fullscreen mode

Now, run the seed command and go and grab a cup of coffee, this will take a while, we are generating 20 users with 2000 chirps each, so it will take a while.

php aritsan db:seed
Enter fullscreen mode Exit fullscreen mode

Implementing Basic Pagination

To prevent page overload, implement basic pagination in the ChirpController:

// app/Http/Controllers/ChirpController.php

public function index(): Response  
{  
    $chirps = Chirp::with('user')->latest()->paginate(25);  

    return \response()->view('chirps.index', [  
        'chirps' => $chirps  
    ]);  
}
Enter fullscreen mode Exit fullscreen mode

Display pagination links in chirps.index:

<!-- resources/views/chirps/index.blade.php -->

<noscript>  
    <div class="max-w-5xl mx-auto p-4 sm:p-6 lg:p-8">  
        {{ $chirps->links() }}  
    </div>  
</noscript>
Enter fullscreen mode Exit fullscreen mode

Adding Infinite Scrolling

Utilize HTMX to implement infinite scrolling while retaining the pagination links as a fallback:

<!-- resources/views/chirps/index.blade.php -->

<div class="max-w-2xl mx-auto p-4 sm:p-6 lg:p-8">  
        <!-- rest of the template -->

        @foreach ($chirps as $chirp)  
            <x-chirps.single :chirp="$chirp" />  
        @endforeach  

        @if($chirps->nextPageUrl())  
            <div  
                hx-get="{{ $chirps->nextPageUrl() }}"  
                hx-select="#chirps>div"  
                hx-swap="outerHTML"  
                hx-trigger="intersect"  
            >  
                Loading more...  
            </div>  
        @endif  
    </div>  
</div>  

<noscript>  
    <div class="max-w-5xl mx-auto p-4 sm:p-6 lg:p-8">  
        {{ $chirps->links() }}  
    </div>  
</noscript>
Enter fullscreen mode Exit fullscreen mode

Ensure x-cloak elements are hidden initially:

<!-- resources/views/layouts/app.blade.php -->

<style>  
    [x-cloak] { display: none !important; }  
</style>
Enter fullscreen mode Exit fullscreen mode

It's called critical because it's loaded before the CSS file is downloaded, that's why you put it in the header and not in the CSS file

The HTMX attributes used in the infinite scrolling code snippet provide the necessary instructions to the browser on how to handle the loading of additional chirps as the user scrolls down the page. Let's break down these attributes:

  1. hx-get="{{ $chirps->nextPageUrl() }}": This attribute instructs HTMX to issue a GET request to the URL specified, which in this case is the URL for the next page of chirps. This action is triggered when the user scrolls down and reaches the "Loading more..." div.
  2. hx-select="#chirps>div": This attribute specifies what part of the returned content from the GET request should be used in the current page. In this case, it's selecting all direct div children of the element with the id #chirps. This will essentially select all the chirps returned in the next page of results.
  3. hx-swap="outerHTML": This attribute specifies how to update the DOM with the selected elements from the GET request. The value outerHTML instructs HTMX to replace the "Loading more..." div entirely with the new content fetched from the server. This way, the newly loaded chirps get appended to the list, and the "Loading more..." div gets replaced by these new chirps.
  4. hx-trigger="intersect": This attribute specifies when to trigger the GET request. The value intersect tells HTMX to trigger the request when the "Loading more..." div comes into view (i.e., when it intersects with the viewport). This is essentially what enables the infinite scrolling behavior, as new chirps are loaded each time the user scrolls down and reaches the end of the current list.
  5. x-cloak: This is an Alpine.js directive, not an HTMX attribute. It's used to hide certain elements initially. When JavaScript is enabled, Alpine.js will remove the x-cloak attribute, making the element visible.

By utilizing these HTMX attributes and the Alpine.js directive, the code is set up to provide a seamless infinite scrolling experience while also having a fallback pagination system for users with JavaScript disabled. This way, the application maintains a good level of accessibility and usability across different user scenarios. How cool is that ?!?!?!

Conclusion

Throughout this tutorial, we've journeyed through enhancing a basic Laravel chirps application with progressively enhanced features utilizing HTMX. We started with simple refinements to form submissions, worked through editing and deleting chirps inline, and finally, implemented infinite scrolling to efficiently load and display a large number of chirps. Each enhancement aimed to provide a more interactive and user-friendly experience while retaining functionality without JavaScript, adhering to the principles of Progressive Enhancement.

The simplicity and power of HTMX shone through, allowing us to keep our code largely server-side, clean, and maintainable, while still offering a dynamic user experience. The transition from traditional server-rendered interactions to a more dynamic interface didn't require a complete rewrite or the introduction of complex client-side frameworks. This stands as a testament to how modern technologies can simplify the development process while adhering to good practices.

Up Next: Dive Deeper into Interactive Features

In the upcoming second part of this tutorial, we'll take our application to the next level by introducing more interactive and real-time features:

  • Spinners: Enhance the user experience by adding the loading indicator
  • Active Search: We'll explore how to implement an active search feature using MySQL full-text indexes to help users find chirps that interest them quickly.
  • Server-Side Validation: Addressing a loose end, we'll work on fixing the server-side validation to ensure data integrity and provide user feedback.
  • Real-Time Updates: Using the pooling method, we'll update our index page with new chirps in real-time, keeping the content fresh and engaging for our users.
  • Experiment with Server-Side Events: We'll take a shot at implementing server-side events to further enhance the real-time interactivity of our application.

The adventure continues as we delve deeper into creating a more interactive and real-time experience without sacrificing the simplicity and maintainability of our Laravel application. Stay tuned, and get ready to explore more ways to bring your Laravel applications to life!

Top comments (5)

Collapse
 
thurzo profile image
Roger

Exceptional work. I just made it through this part and know that htmx is a great fit for me and my projects. Looking forward to the next parts. Thanks so much for you efforts putting this together.

Collapse
 
hussain2y2 profile image
Hussain Ahmad

What a great post, put a lot of effort. It is really clear and concise, making it easier for the developers like me to grasp the concepts and apply in their real world programming. I look forward to reading more of your posts in the future and continuing to learn from your experiences.

Collapse
 
selase profile image
selase

Well done Turcu. It feels so 'natural'. This is how it should have been right from the beginning. I can't wait for more content on this stuff. Please consider adding HTMX Modal, Validations, and validations on modals etc.

Collapse
 
selase profile image
selase

Fantastic! Turcu. It feels so natural, as though this is how it should have been right from the beginning. Thanks for putting it together and I sincerely cannot wait for more content on this stuff. Please consider Modals, Validations (how to return error messages to an HTMX modal), etc.

Collapse
 
turculaurentiu91 profile image
Turcu Laurentiu

Thank you, I will try to do the second part this weekend!