Laravel + HTMX = ❤️
Tags: laravel, php, htmx, webdev
Published: 2023-10-03 16:10:53

## 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](https://bootcamp.laravel.com/blade/installation) Chirper blade project. As a primer, feel free to explore the live implementation at [chirper.tlaurentiu.net](http://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](https://htmx.org/)

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](https://dev.to/turculaurentiu91/why-you-should-choose-htmx-for-your-next-project-o7j).

## 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](https://github.com/turculaurentiu91/chirper-htmx). 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.

```html
<!-- 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>
```

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:

```html
<!-- 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>
```

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

```html
<!-- 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 -->

```

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!

```html
<!-- 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 -->
```

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](https://htmx.org/docs/#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.

```html
<!-- 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>
```

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.

```php
// 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'));  
}
```

### The Single Chirp Component

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

```html
<!-- 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>
```

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.

```php
// 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,  
    ]);  
}
```

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

```html
<!-- 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>
```

```php
public function show(Chirp $chirp): Response  
{  
    return response()->view('components.chirps.single', [  
        'chirp' => $chirp,  
    ]);  
}
```

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:

```php
// 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');  
}
```

### 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:

```html
<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">
```

### 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:

```html
<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();"  
>
```

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.

```php
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'));  
}
```

### 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:

```html
<!-- 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
```
*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.

```html

<!-- 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>
```

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:

```bash
php artisan make:factory ChirpFactory --model Chirp
```

```php
<?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,  
        ];  
    }  
}
```

```php

// DatabaseSeeder.php

public function run(): void  
{  
    User::factory(20)->hasChirps(2000)->create();  
}
```

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. 

```bash
php aritsan db:seed
```

### Implementing Basic Pagination

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

```php
// app/Http/Controllers/ChirpController.php

public function index(): Response  
{  
    $chirps = Chirp::with('user')->latest()->paginate(25);  
  
    return \response()->view('chirps.index', [  
        'chirps' => $chirps  
    ]);  
}
```

Display pagination links in `chirps.index`:

```html
<!-- 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>
```

### Adding Infinite Scrolling

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

```html
<!-- 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>
```

Ensure `x-cloak` elements are hidden initially:

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

<style>  
    [x-cloak] { display: none !important; }  
</style>
```
*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!