Explore Developer Center's New Chatbot! MongoDB AI Chatbot can be accessed at the top of your navigation to answer all your MongoDB questions.

Join us at AWS re:Invent 2024! Learn how to use MongoDB for AI use cases.
MongoDB Developer
Atlas
plus
Sign in to follow topics
MongoDB Developer Centerchevron-right
Developer Topicschevron-right
Productschevron-right
Atlaschevron-right

Adding Autocomplete to Your Laravel Applications With MongoDB Atlas Search

Abdulrasaq Jamiu Adewuyi9 min read • Published Nov 26, 2024 • Updated Dec 03, 2024
SearchPHPAtlas
Facebook Icontwitter iconlinkedin icon
Rate this tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
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.
Demo showing how the MongoDB Atlas Search is implemented in a Lavarel Application
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 for 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.
1composer 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.
Laravel project file structure
From the terminal, run the below code to start the server:
1cd movieapp
2php 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.
Laravel bootstrap application default home page design
Also, in a new terminal window, run the below command to start the front-end server.
1npm install
2npm 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.
    1composer require mongodb/Laravel-mongodb
    this will add the MongoDB package to the vendor directory
  2. 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.
    1composer require mongodb/builder:^0.2
  3. Navigate to the .env. Let’s update the DB_CONNECTION value and add a DB_URL as below:
1 DB_CONNECTION=mongodb
2 DB_URI=mongodb_connection_string_here
Update the text mongodb_connection_string_here with your database connection string.
4. Navigate to the config/database.php file and update the connection array as below:
1 'connections' => [
2 'mongodb' => [
3 'driver' => 'mongodb',
4 'dsn' => env('DB_URI'),
5 'database' => 'sample_mflix',
6 ],
5. Still on the config/database.php file, update the default string as:
1'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:
1<?php
2use Illuminate\Support\Facades\Route;
3// Import the Request class
4use Illuminate\Http\Request;
5// Import the DB facade
6use Illuminate\Support\Facades\DB;
7Route::get('/', function () {
8 return view('welcome');
9});
10//add ping route
11Route::get('/connect', function (Request $request) {
12 $connection = DB::connection('mongodb');
13 $msg = 'MongoDB is accessible!';
14 try { $connection->getMongoClient()->selectDatabase($connection->getDatabaseName())->command(['ping' => 1]);
15 } catch (\Exception $e) {
16 $msg = 'MongoDB is not accessible. Error: ' . $e->getMessage();
17 }
18 return response()->json(['msg' => $msg]);
19});
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," the 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.
    1php artisan make:model Movie
    Once the command has finished running, it will create /app/Models/Movie.php. Open the file and update as below:
    1<?php
    2namespace App\Models;
    3use MongoDB\Laravel\Eloquent\Model;
    4class Movie extends Model
    5{
    6protected $connection = 'mongodb';
    7}
    With the model created, let's display some of the movies on our home page.
    To do this, update the /routes/web.php:
    1<?php
    2use Illuminate\Support\Facades\Route;
    3use App\Models\Movie;
    4Route::get('/', function () {
    5 $movies = Movie::limit(20)->get(); // Retrieve only 20 movies
    6 return view('welcome', [
    7 'movies' => $movies
    8 ]);
    9});
    Then, update the body tag of app/resources/views/welcome.blade.php, as below.
    1<body class="font-sans antialiased dark:bg-black dark:text-white/50">
    2 <div class="bg-gray-50 text-black/50 dark:bg-black dark:text-white/50">
    3 <img id="background" class="absolute -left-20 top-0 max-w-[877px]"
    4 src="https://Laravel.com/assets/img/welcome/background.svg" alt="Laravel background"/>
    5 <div
    6 class="relative min-h-screen flex flex-col items-center justify-center selection:bg-[#FF2D20] selection:text-white">
    7 <div class="relative w-full max-w-2xl px-6 lg:max-w-7xl">
    8 <div id="movie-list" class=' w-full flex gap-6 justify-around items-center flex-wrap'>
    9 </div>
    10 </div>
    11 </div>
    12 <script>
    13 let movies = @json($movies);
    14 displayMovies(movies)
    15 function displayMovies(movies) {
    16 const movieListDiv = document.getElementById('movie-list');
    17 movieListDiv.innerHTML = movies.map(movie => `
    18 <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">
    19 <div class="h-[400px]">
    20 <img class="rounded-t-lg object-cover w-full h-full" src="${movie.poster}" alt="${movie.title}" />
    21 </div>
    22 <div class="p-5">
    23 <a href="#">
    24 <h5 class="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white line-clamp-1">
    25 ${movie.title}
    26 </h5>
    27 </a>
    28 <p class="mb-3 font-normal text-gray-700 dark:text-gray-400 line-clamp-2">${movie.plot}</p>
    29 <a href="#"
    30 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">
    31 See more
    32</a>
    33 </div>
    34 </div>
    35 `).join('');
    36}
    37 </script>
    38 </body>
    Refresh your application on http://localhost:8000. You should have a list of movies displayed on the screen, as below.
    Showing list of movies from an API response
    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.
    1{
    2 "mappings": {
    3 "dynamic": false,
    4 "fields": {
    5 "title": {
    6 "type": "string"
    7 }
    8 }
    9 }
    10}
    MongoDB Atlas Search creation using the JSON editor

    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:
    MongoDB Atlas Search Autocomplete creation using the JSON editor
    1{
    2 "mappings": {
    3 "dynamic": false,
    4 "fields": {
    5 "title": {
    6 "type": "autocomplete"
    7 }
    8 }
    9 }
    10}

    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.
    1<?php
    2namespace App\Models;
    3use Illuminate\Support\Collection;
    4use MongoDB\Laravel\Eloquent\Model;
    5
    6class Movie extends Model
    7{
    8 protected $connection = 'mongodb';
    9
    10 public static function searchByTitle(string $input): Collection
    11 {
    12 return self::aggregate()
    13 ->search([
    14 'index' => 'movie_search',
    15 'compound' => [
    16 'must' => [
    17 [
    18 'text' => [
    19 'query' => $input,
    20 'path' => 'title',
    21 'fuzzy' => ['maxEdits' => 2] // Adding fuzzy matching
    22 ]
    23 ]
    24 ]
    25 ]
    26 ])
    27 ->limit(20)
    28 ->project(title: 1, genres: 1, poster: 1, rated: 1, plot: 1)
    29 ->get();
    30 }
    31
    32 public static function autocompleteByTitle(string $input): Collection
    33 {
    34 return self::aggregate()
    35 ->search([
    36 'index' => 'movie_title_autocomplete',
    37 'autocomplete' => [
    38 'query' => $input,
    39 'path' => 'title'
    40 ],
    41 'highlight' => [
    42 'path' => ['title']
    43 ]
    44 ])
    45 ->limit(5) // Limit the result to 5
    46 ->project(title: 1, highlights: ['$meta' => 'searchHighlights'])
    47 ->get();
    48 }
    49}
    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.
    1php artisan make:controller SearchController
    This command will create an app/Http/Controllers/SearchController.php file. Let's update the file as below.
    1<?php
    2namespace App\Http\Controllers;
    3use App\Models\Movie;
    4use Illuminate\Http\JsonResponse;
    5
    6class SearchController extends Controller
    7{
    8 public function search($search): JsonResponse
    9 {
    10 // Define the aggregation based on the search conditions
    11 if (!empty($search)) {
    12 $items = Movie::searchByTitle($search);
    13 return response()->json($items, 200);
    14 }
    15 return response()->json(['error' => 'conditions not met'], 400);
    16 }
    17}
    Next, let's create an API route.
    Navigate to app/routes/web.php:
    1// import the SearchController
    2use App\Http\Controllers\SearchController;
    3
    4Route::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.
    1public function autocomplete($param): JsonResponse
    2 {
    3 try {
    4 $results = Movie::autocompleteByTitle($param);
    5 return response()->json($results, 200);
    6 } catch (\Exception $e) {
    7 return response()->json(['error' => $e->getMessage()], 500);
    8 }
    9 }
    10 }
    Next, let's create an API route. Navigate to app/routes/web.php.
    1Route::get('/autocomplete/{param}', [SearchController::class, 'autocomplete']);
    Test the API by calling it like so:
    1http://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.
    1<body class="font-sans antialiased dark:bg-black dark:text-white/50">
    2 <script>
    3 let debounceTimer;
    4 let movies = @json($movies);
    5 displayMovies(movies)
    6
    7 function handleSearch(event) {
    8 const query = event.target.value;
    9 // Clear the previous debounce timer
    10 clearTimeout(debounceTimer);
    11 // Set up a new debounce timer
    12 debounceTimer = setTimeout(() => {
    13 if (query.length > 2) { // Only search when input is more than 2 characters
    14 titleAutocomplete(query);
    15 }
    16 }, 300);
    17 }
    18
    19 async function titleAutocomplete(query) {
    20 try {
    21 const response = await fetch(`/autocomplete/${encodeURIComponent(query)}`);
    22 const movies = await response.json();
    23 displayResults(movies);
    24 } catch (error) {
    25 console.error('Error fetching movies:', error);
    26 }
    27 }
    28
    29 async function fetchMovies(query) {
    30 try {
    31 const response = await fetch(`/search/${encodeURIComponent(query)}`);
    32 const movies = await response.json();
    33 displayMovies(movies);
    34 displayResults([])
    35 } catch (error) {
    36 console.error('Error fetching movies:', error);
    37 }
    38 }
    39
    40 function displayResults(movies) {
    41 const resultsDiv = document.getElementById('search-results');
    42 resultsDiv.innerHTML = movies.map(movie => ` <div onclick="fetchMovies('${movie.title}')" class='select-none text-gray-600 cursor-pointer flex items-center gap-[10px]'>
    43 <div>
    44 ${movie.title}
    45 </div>
    46 </div>`).join('');
    47 }
    48
    49
    50 function displayMovies(movies) {
    51 const movieListDiv = document.getElementById('movie-list');
    52 movieListDiv.innerHTML = movies.map(movie => `
    53 <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">
    54 <div class="h-[400px]">
    55 <img class="rounded-t-lg object-cover w-full h-full" src="${movie.poster}" alt="${movie.title}" />
    56 </div>
    57 <div class="p-5">
    58 <a href="#">
    59 <h5 class="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white line-clamp-1">
    60 ${movie.title}
    61 </h5>
    62 </a>
    63 <p class="mb-3 font-normal text-gray-700 dark:text-gray-400 line-clamp-2">${movie.plot}</p>
    64 <a href="#"
    65 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">
    66 See more
    67 <svg class="rtl:rotate-180 w-3.5 h-3.5 ms-2" aria-hidden="true"
    68 xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 10">
    69 <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
    70 stroke-width="2" d="M1 5h12m0 0L9 1m4 4L9 9" />
    71 </svg>
    72 </a>
    73 </div>
    74 </div>
    75 `).join('');
    76 }
    77 </script>
    78 </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.
    Demo showing how the MongoDB Atlas Search is implemented in a Lavarel Application

    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.
    Do you have questions or comments? Let's continue the conversation! Head over to the MongoDB Developer Community we'd love to hear from you.
    You can connect with me on Twitter.
    Happy coding.

    Facebook Icontwitter iconlinkedin icon
    Rate this tutorial
    star-empty
    star-empty
    star-empty
    star-empty
    star-empty
    Related
    Tutorial

    Influence Search Result Ranking with Function Scores in Atlas Search


    Feb 03, 2023 | 5 min read
    Article

    Deploying MongoDB Atlas With Terraform with Azure


    Jun 18, 2024 | 7 min read
    Tutorial

    Getting Started With Atlas Stream Processing Security


    May 17, 2024 | 9 min read
    News & Announcements

    Deprecating MongoDB Atlas GraphQL and Hosting Services


    Mar 12, 2024 | 2 min read
    Table of Contents