Laravel Cloud is here! Zero-config managed infrastructure for Laravel apps. Deploy now.

Adding Autocomplete to Your Laravel Applications

Last updated on by

Adding Autocomplete to Your Laravel Applications image

Implementing a search feature has become hugely important for every web app that values user experience, as users can search for what they need to see without scrolling endlessly.

In this tutorial, we will build a movie application leveraging MongoDB Atlas Search with Laravel (a leading PHP framework) to build a rich text-based search feature that allows users to search for movies by typing a few letters.

Why use MongoDB Atlas Search?

MongoDB's flexible schema, Atlas Search, and powerful querying capabilities, combined with Laravel's expressive syntax, enable us to implement advanced search functionalities quickly.

The source of this application can be found on GitHub.

Prerequisites

Set up database

To set up our MongoDB environment, create a database cluster, set database access, and get the database connection string, we must follow the below instructions from the MongoDB official documentation.

Identify a database to work with

Once we have our cluster set up, we need to load some sample data, we will work with this implementation and get our connection string.

Your connection string should look like this:

mongodb+srv://user:password@cluster0.xxxxx.mongodb.net

If you complete the above steps, the sample data should be loaded in your cluster. Otherwise, check out the documentation on how to load sample data.

After successfully loading the data, we should have a database called sample_mflix within our cluster. We will work with the movies collection in the database.

Laravel and MongoDB packages installation and environment setup

With the database set up, let’s create the Laravel project with Composer. To continue, you want to make sure you have PHP, Laravel, Node, npm, Composer, and finally, the MongoDB PHP extension all properly set up. The following links will come in handy.

Check out the instructions for the MongoDB and Laravel integration. They explain how to configure a Laravel-MongoDB development environment. We'll cover the Laravel application creation and the MongoDB configuration below.

Create a Laravel project

With our development environment working, to create a Laravel project, we will use Composer. Run the below code in the term in your preferred directory.

composer create-project laravel/laravel movieapp

This command will create a new Laravel project in the folder movieapp. After completing installation, your folder structure should look as below.

From the terminal, run the below code to start the server:

cd movieapp
php artisan serve

Once the server is running, it should be available at http://localhost:8000/. You should see the Laravel starter page, as shown below.

Also, in a new terminal window, run the below command to start the front-end server.

npm install
npm run dev

With our server up and running, let's connect to our MongoDB cluster.

Connect the Laravel server to MongoDB

To connect the server to the MongoDB cluster we created earlier, follow as below:

  1. Firstly, Composer lets you add the Laravel MongoDB package to the application. In the command prompt, go to the project's directory and run the command below.
composer require mongodb/laravel-mongodb

this will add the MongoDB package to the vendor directory

  1. Let us use Composer to add another package called mongodb/builder. We will be using this to build an aggregation pipeline later in this guide. Run the below command to add mongodb/builder.
composer require mongodb/builder:^0.2
  1. Navigate to the .env. Let’s update the DB_CONNECTION value and add a DB_URI as below:
DB_CONNECTION=mongodb
DB_URI=mongodb_connection_string_here

Update the text mongodb_connection_string_here with your database connection string.

  1. Navigate to the config/database.php file and update the connections array as below:
'connections' => [
'mongodb' => [
'driver' => 'mongodb',
'dsn' => env('DB_URI'),
'database' => 'sample_mflix',
],
  1. Still on the config/database.php file, update the default string as:
'default' => env('DB_CONNECTION'),

This is to set MongoDB as the default connection for the application. With these variables updated, we should be able to connect to MongoDB successfully,

Let's create a route in the /routes/web.php:

<?php
use Illuminate\Support\Facades\Route;
// Import the Request class
use Illuminate\Http\Request;
// Import the DB facade
use Illuminate\Support\Facades\DB;
 
Route::get('/', function () {
return view('welcome');
});
 
//add ping route
Route::get('/connect', function (Request $request) {
$connection = DB::connection('mongodb');
$msg = 'MongoDB is accessible!';
 
try {
$connection->getMongoClient()
->selectDatabase($connection->getDatabaseName())
->command(['ping' => 1]);
} catch (\Exception $e) {
$msg = 'MongoDB is not accessible. Error: ' . $e->getMessage();
}
 
return response()->json(['msg' => $msg]);
});

In the terminal, run php artisan route.

You should see the route in the list. In the browser, navigate to http://localhost:8000/connect/. You should see a success message {"msg":"MongoDB is accessible!"}.

Create a movie model (MongoDB Eloquent model)

Let's create an Eloquent model for our MongoDB database named "Movie" since we will be working with a collection named movies. By convention, the "snake case," plural name of the class, will be used as the collection name unless another name is explicitly specified. So, in this case, Eloquent will assume the Movie model stores documents in the movies collection. Run the command from the project's directory to create the Movie model.

php artisan make:model Movie

Once the command has finished running, it will create /app/Models/Movie.php. Open the file and update as below:

<?php
namespace App\Models;
 
use MongoDB\Laravel\Eloquent\Model;
 
class Movie extends Model
{
protected $connection = 'mongodb';
}

With the model created, let's display some of the movies on our home page.
To do this, update the /routes/web.php:

<?php
use Illuminate\Support\Facades\Route;
use App\Models\Movie;
 
Route::get('/', function () {
$movies = Movie::limit(20)->get(); // Retrieve only 20 movies
return view('welcome', [
'movies' => $movies
]);
});

Then, update the body tag of app/resources/views/welcome.blade.php, as below.

<body class="font-sans antialiased dark:bg-black dark:text-white/50">
<div class="bg-gray-50 text-black/50 dark:bg-black dark:text-white/50">
<img id="background" class="absolute -left-20 top-0 max-w-[877px]"
src="https://laravel.com/assets/img/welcome/background.svg" alt="Laravel background" />
<div
class="relative min-h-screen flex flex-col items-center justify-center selection:bg-[#FF2D20] selection:text-white">
<div class="relative w-full max-w-2xl px-6 lg:max-w-7xl">
<div id="movie-list" class=' w-full flex gap-6 justify-around items-center flex-wrap'>
</div>
</div>
</div>
<script>
let movies = @json($movies);
displayMovies(movies)
 
function displayMovies(movies) {
const movieListDiv = document.getElementById('movie-list');
movieListDiv.innerHTML = movies.map(movie => `
<div class="movie-card max-w-sm bg-green-500 border border-gray-200 rounded-lg shadow dark:bg-gray-800 dark:border-gray-700">
<div class="h-[400px]">
<img class="rounded-t-lg object-cover w-full h-full" src="${movie.poster}" alt="${movie.title}" />
</div>
<div class="p-5">
<a href="#">
<h5 class="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white line-clamp-1">
${movie.title}
</h5>
</a>
<p class="mb-3 font-normal text-gray-700 dark:text-gray-400 line-clamp-2">${movie.plot}</p>
<a href="#"
class="inline-flex items-center px-3 py-2 text-sm font-medium text-center text-white bg-blue-700 rounded-lg hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">
See more
</a>
</div>
</div>
`).join('');
}
</script>
</body>

Refresh your application on http://localhost:8000. You should have a list of movies displayed on the screen, as below.

Did you get an error, E11000 duplicate key error collection: sample_mflix.sessions index: user_id_1 dup key: { user_id: null }? In this case, head over to the sessions collection in sample_mflix database, clear the document in it, and try again.

With the application running, let's head over to the MongoDB Atlas dashboard to create an Atlas search index. But before that…

Create an Atlas Search index

To implement the feature such that we can search movies by their title, let’s create a search index from the MongoDB Atlas dashboard.

From the previously created cluster, click on Browse collections, navigate to the Atlas Search tab, and click on Create index on the right side of the search page. On this screen, select JSON editor under Atlas Search, click Next to proceed, add an index name (in our case, movie_search), select the movies collection from the sample_mflix database, and update the JSON editor as below. Click Next.

{
"mappings": {
"dynamic": false,
"fields": {
"title": {
"type": "string"
}
}
}
}

Create an Atlas Search autocomplete index

Next, let's add another search index, but this time, the type will be autocomplete. We will use it to implement a feature such that when a user types in the input box, it will suggest possible movie titles.

Create another index, give it a name (in our case, movie_title_autocomplete), and update the JSON editor, as below:

{
"mappings": {
"dynamic": false,
"fields": {
"title": {
"type": "autocomplete"
}
}
}
}

Create search routes API endpoint with Laravel's Eloquent

Let’s create two functions called searchByTitle and autocompleteByTitle within the Movie model class. These functions will implement the search and the autocomplete features, respectively.

Therefore, let’s update the app/Models/Movie.php file as below.

<?php
namespace App\Models;
 
use Illuminate\Support\Collection;
use MongoDB\Laravel\Eloquent\Model;
 
class Movie extends Model
{
protected $connection = 'mongodb';
 
public static function searchByTitle(string $input): Collection
{
return self::aggregate()
->search([
'index' => 'movie_search',
'compound' => [
'must' => [
[
'text' => [
'query' => $input,
'path' => 'title',
'fuzzy' => ['maxEdits' => 2] // Adding fuzzy matching
]
]
]
]
])
->limit(20)
->project(title: 1, genres: 1, poster: 1, rated: 1, plot: 1)
->get();
}
 
public static function autocompleteByTitle(string $input): Collection
{
return self::aggregate()
->search([
'index' => 'movie_title_autocomplete',
'autocomplete' => [
'query' => $input,
'path' => 'title'
],
'highlight' => [
'path' => ['title']
]
])
->limit(5) // Limit the result to 5
->project(title: 1, highlights: ['$meta' => 'searchHighlights'])
->get();
}
}

Note: Putting the code that makes the search query in the model class separates the data access layer from the http controllers. This makes the code more testable.

Let’s create a controller to implement the search API in the project. Let's run the code below to create a controller.

php artisan make:controller SearchController

This command will create an app/Http/Controllers/SearchController.php file. Let's update the file as below.

<?php
namespace App\Http\Controllers;
 
use App\Models\Movie;
use Illuminate\Http\JsonResponse;
 
class SearchController extends Controller
{
public function search($search): JsonResponse
{
// Define the aggregation based on the search conditions
if (!empty($search)) {
$items = Movie::searchByTitle($search);
return response()->json($items, 200);
}
return response()->json(['error' => 'conditions not met'], 400);
}
}

Next, let's create an API route.

Navigate to app/routes/web.php:

// import the SearchController
use App\Http\Controllers\SearchController;
 
Route::get('/search/{search}', [SearchController::class, 'search']);

Test the API by calling it, like so:
http://localhost:8000/search/moving train

Create autocomplete routes API endpoint with Laravel's Eloquent

Navigate to the app/Http/Controllers/SearchController.php file and add the below function after the end of the search function.

public function autocomplete($param): JsonResponse
{
try {
$results = Movie::autocompleteByTitle($param);
return response()->json($results, 200);
} catch (\Exception $e) {
return response()->json(['error' => $e->getMessage()], 500);
}
}

Next, let's create an API route. Navigate to app/routes/web.php.

Route::get('/autocomplete/{param}', [SearchController::class, 'autocomplete']);

Test the API by calling it like so:

http://localhost:8000/autocomplete/hello

Implement autocomplete in our front-end application

To see these implements in action, let's update the body tag of the app/resources/views/welcome.blade.php as below.

Get the complete code snippet on GitHub.

<body class="font-sans antialiased dark:bg-black dark:text-white/50">
<script>
let debounceTimer;
let movies = @json($movies);
displayMovies(movies)
 
function handleSearch(event) {
const query = event.target.value;
// Clear the previous debounce timer
clearTimeout(debounceTimer);
// Set up a new debounce timer
debounceTimer = setTimeout(() => {
if (query.length > 2) { // Only search when input is more than 2 characters
titleAutocomplete(query);
}
}, 300);
}
 
async function titleAutocomplete(query) {
try {
const response = await fetch(`/autocomplete/${encodeURIComponent(query)}`);
const movies = await response.json();
displayResults(movies);
} catch (error) {
console.error('Error fetching movies:', error);
}
}
 
async function fetchMovies(query) {
try {
const response = await fetch(`/search/${encodeURIComponent(query)}`);
const movies = await response.json();
displayMovies(movies);
displayResults([])
} catch (error) {
console.error('Error fetching movies:', error);
}
}
 
function displayResults(movies) {
const resultsDiv = document.getElementById('search-results');
resultsDiv.innerHTML = movies.map(movie => `
<div onclick="fetchMovies('${movie.title}')" class='select-none text-gray-600 cursor-pointer flex items-center gap-[10px]'>
<div>
${movie.title}
</div>
</div>`).join('');
}
 
 
function displayMovies(movies) {
const movieListDiv = document.getElementById('movie-list');
movieListDiv.innerHTML = movies.map(movie => `
<div class="movie-card max-w-sm bg-green-500 border border-gray-200 rounded-lg shadow dark:bg-gray-800 dark:border-gray-700">
<div class="h-[400px]">
<img class="rounded-t-lg object-cover w-full h-full" src="${movie.poster}" alt="${movie.title}" />
</div>
<div class="p-5">
<a href="#">
<h5 class="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white line-clamp-1">
${movie.title}
</h5>
</a>
<p class="mb-3 font-normal text-gray-700 dark:text-gray-400 line-clamp-2">${movie.plot}</p>
<a href="#"
class="inline-flex items-center px-3 py-2 text-sm font-medium text-center text-white bg-blue-700 rounded-lg hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">
See more
<svg class="rtl:rotate-180 w-3.5 h-3.5 ms-2" aria-hidden="true"
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 10">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M1 5h12m0 0L9 1m4 4L9 9" />
</svg>
</a>
</div>
</div>
`).join('');
}
</script>
</body>

In the above changes:

  • We added a function called displayMovies, which takes movies as an argument. It will render movie cards to the screen based on the movies list.
  • Then, we have a function called handleSearch which is an oninput event handler for the search input box.
  • Within the handleSearch function, we have a function called titleAutocomplete, which fetches and displays data from the autocomplete API endpoint.
  • Then, we have the fetchMovies function, which fetches data from the search API endpoint within which we call the displayMovies function to display the movie’s response for the API.

With all these changes made, head over to http://localhost:8000/ to test, as shown below.

Conclusion

It is crucial to make it easy for your users to find what they are looking for on a website to have a great user experience. In this guide, I showed you how I created a text search for a movie application with MongoDB Atlas Search. This search will allow users to search for movies by their title.

Atlas Search is a full-text search engine that enables developers to implement rich search functionality into their applications. It allows users to search large quantities of data quickly and efficiently.

Learn more about MongoDB Atlas Search and using PHP with MongoDB.

Abdulrasaq Jamiu Adewuyi photo

MongoDB Creator || Software engineer || JavaScript || TypeScript || Technical writer || AWS

Cube

Laravel Newsletter

Join 40k+ other developers and never miss out on new tips, tutorials, and more.

image
Laravel Code Review

Get expert guidance in a few days with a Laravel code review

Visit Laravel Code Review
Curotec logo

Curotec

World class Laravel experts with GenAI dev skills. LATAM-based, embedded engineers that ship fast, communicate clearly, and elevate your product. No bloat, no BS.

Curotec
Bacancy logo

Bacancy

Supercharge your project with a seasoned Laravel developer with 4-6 years of experience for just $3200/month. Get 160 hours of dedicated expertise & a risk-free 15-day trial. Schedule a call now!

Bacancy
Tinkerwell logo

Tinkerwell

The must-have code runner for Laravel developers. Tinker with AI, autocompletion and instant feedback on local and production environments.

Tinkerwell
Get expert guidance in a few days with a Laravel code review logo

Get expert guidance in a few days with a Laravel code review

Expert code review! Get clear, practical feedback from two Laravel devs with 10+ years of experience helping teams build better apps.

Get expert guidance in a few days with a Laravel code review
PhpStorm logo

PhpStorm

The go-to PHP IDE with extensive out-of-the-box support for Laravel and its ecosystem.

PhpStorm
Laravel Cloud logo

Laravel Cloud

Easily create and manage your servers and deploy your Laravel applications in seconds.

Laravel Cloud
Kirschbaum logo

Kirschbaum

Providing innovation and stability to ensure your web application succeeds.

Kirschbaum
Shift logo

Shift

Running an old Laravel version? Instant, automated Laravel upgrades and code modernization to keep your applications fresh.

Shift
Harpoon: Next generation time tracking and invoicing logo

Harpoon: Next generation time tracking and invoicing

The next generation time-tracking and billing software that helps your agency plan and forecast a profitable future.

Harpoon: Next generation time tracking and invoicing
Lucky Media logo

Lucky Media

Get Lucky Now - the ideal choice for Laravel Development, with over a decade of experience!

Lucky Media
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit: Laravel SaaS Starter Kit

SaaSykit is a Multi-tenant Laravel SaaS Starter Kit that comes with all features required to run a modern SaaS. Payments, Beautiful Checkout, Admin Panel, User dashboard, Auth, Ready Components, Stats, Blog, Docs and more.

SaaSykit: Laravel SaaS Starter Kit

The latest

View all →
Bagisto Visual: Theme Framework with Visual Editor for Laravel E-commerce image

Bagisto Visual: Theme Framework with Visual Editor for Laravel E-commerce

Read article
Clawdbot Rebrands to Moltbot After Trademark Request From Anthropic image

Clawdbot Rebrands to Moltbot After Trademark Request From Anthropic

Read article
Automate Laravel Herd Worktrees with This Claude Code Skill image

Automate Laravel Herd Worktrees with This Claude Code Skill

Read article
Laravel Boost v2.0 Released with Skills Support image

Laravel Boost v2.0 Released with Skills Support

Read article
Laravel Debugbar v4.0.0 is released image

Laravel Debugbar v4.0.0 is released

Read article
Radiance: Generate Deterministic Mesh Gradient Avatars in PHP image

Radiance: Generate Deterministic Mesh Gradient Avatars in PHP

Read article