Support the ongoing development of Laravel.io →

Build a Quick & Easy Instant Search User Interface using Alpine AJAX & Laravel

7 Nov, 2023 5 min read

I’m going to walk you through how to easily add an instant search filter in your Laravel apps. When I say “instant search” I mean a text input that filters a list of results as you type. In this tutorial we’ll build out a basic contact list and then allow a user to search for a contact by name or email. We'll build the "search as you type" behavior, without writing any JavaScript, using the Alpine AJAX library. This UI pattern is one of my favorite examples that demonstrates the power and simplicity of Alpine AJAX.

The Data

You can start with a fresh Laravel install, get a database ready to go, then run php artisan migrate in the terminal to generate the “users” table. To ensure that you’ve got user data to work with, you can run php artisan tinker to enter the Tinker CLI, then User::factory()->count(20)->create(); to generate 20 users in your database.

The View

Since we're focusing on the user interface, let's start with the view so we can get a sense of how things will look and feel. We'll create a Blade template at resources/views/contacts.blade.php. It’s going to be a basic page with a search form followed by a list of contacts. I’m leaving CSS styling out of this tutorial so that we can focus on writing good markup, style things however you’d like.

<!-- resources/views/contacts.blade.php -->

<!doctype html>
<html>
<head>
  <title>Contacts</title>
</head>
<body>
  <h1>Contacts</h1>
  <form role="search" aria-label="Contacts">
    <label for="term">Search</label>
    <input type="search" id="term" name="term">
    <button>Submit</button>
  </form>
  <h2>Results</h2>
  <ul role="list">
    @foreach($contacts as $contact)
      <li>{{ $contact->name }} – {{ $contact->email }}</li>
    @endforeach
  </ul>
<body>
</html>

We've left the action and method attributes off of the search <form> so by default the form will issue a GET request to the current URL. The search <input> has the name “term” so that we can access the submitted search team on the backend. Next, we'll scaffold out some backend logic for this form.

The Route

Now let's create a new route at /contacts. Our route logic will check if the incoming request contains a value for “term”, if so, it’ll query the “users” table for a record with a “name” or “email” that contains the search term:

// routes/web.php

use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::get('/contacts', function (Request $request) {
    $contacts = User::when($request->term, function ($query, $term) {
        $query->where(function ($query) use ($term) {
            $query->where('name', 'like', "%{$term}%")->orWhere('email', 'like', "%{$term}%");
        });
    })->get();

    return view('contacts', [
        'contacts' => $contacts,
    ]);
});

Note that this is a simple search implementation just for demonstration purposes. You’d probably be better off using something like Laravel Scout for database searches, but that’s beyond the scope of this tutorial.

At this point you should be able to navigate to /contacts in your browser, submit the search form, and see that the page reloads with an updated list of contacts. We've got our basic search form working! Now we can layer on extra features to make it feel really good to use.

The Interaction

It’s time to enhance our search form so that we get instant results as we type. This is where Alpine AJAX shines. Alpine AJAX is a small Alpine.js plugin that provides an easy way to make AJAX requests and render content on the page.

First we’ll get Alpine and Alpine AJAX installed; we’ll add two script tags in the page <head>, but you can also install these libraries through NPM if you’d like:

<!-- resources/views/contacts.blade.php -->

<head>
  <title>Contacts</title>
  <script defer src="https://cdn.jsdelivr.net/npm/@imacrayon/[email protected]/dist/cdn.min.js"></script>
  <script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js"></script>
</head>

Next, let’s add a few attributes to our search elements:

<!-- resources/views/contacts.blade.php -->

<h1>Contacts</h1>
<form role="search" aria-label="Contacts" x-init x-target="contacts">
  <label for="term">Search</label>
  <input type="search" id="term" name="term" @input.debounce="$el.form.requestSubmit()">
  <button x-show="false">Submit</button>
</form>
<h2>Results</h2>
<ul id="contacts">
  @foreach($contacts as $contact)
    <li>{{ $contact->name }} – {{ $contact->email }}</li>
  @endforeach
</ul>

Here's a breakdown of the attributes we've added:

  1. x-init on the search form initializes our Alpine component.
  2. id="contacts" on the list of contacts allows our search form the target the list.
  3. x-target="contacts" on the search form changes the behavior of the form: When it is submitted an AJAX request is issued to /contacts and the updated <ul id="contacts"> returned in the response replaces the existing contact list on the page.
  4. @input.debounce on the search input automatically submits the search form when the value of the input is changed.
  5. Finally, we’ve added x-show="false" to the search form’s submit button. This is a small progressive enhancement that will ensure the search form stays usable with or without JavaScript. When JavaScript is loaded, the button is hidden, but if JavaScript fails to load, the button stays on the page and provides a way for the user to manually submit a search term.

That’s it! With the new Blade markup in place, refresh the page (make sure to clear out the ?term query string in the URL if it's there). Now you should see the contact list magically update as you type.

Check out the Alpine AJAX examples page to see more UI examples in action. It’s wild to see all the things you can accomplish with such a small JavaScript library.

Last updated 3 months ago.

driesvints, imacrayon, jasko15modrac liked this article

3
Like this article? Let the author know and give them a clap!
imacrayon (Christian Taylor) An award-losing web developer & visual artist from Wichita, KS. He/Him.

Other articles you might like

July 19th 2024

Standardizing API Responses Without Traits

Problem I've noticed that most libraries created for API responses are implemented using traits, and...

Read article
July 17th 2024

Collect feedback via Discord notifications in your Laravel project

How to create a feedback module in a Laravel project and receive a Discord notification when a messa...

Read article
July 12th 2024

Laravel Advanced: Top 10 Validation Rules You Didn't Know Existed

Do you know all the validation rules available in Laravel? Think again! Laravel has many ready-to-us...

Read article

We'd like to thank these amazing companies for supporting us

Your logo here?

Laravel.io

The Laravel portal for problem solving, knowledge sharing and community building.

© 2024 Laravel.io - All rights reserved.