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 25, 2024 • Updated Nov 25, 2024
AtlasPHP
FULL APPLICATION
Facebook Icontwitter iconlinkedin icon
queryable encryption
Rate this article
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 Altas 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 `Altas Search` tab, and click on `Create index' at 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](https://github.com/jaymeeu/laravel-atlas-search/blob/main/resources/views/welcome.blade.php).
    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 Altas 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.
    Top Comments in Forums
    There are no comments on this article yet.
    Start the Conversation

    Facebook Icontwitter iconlinkedin icon
    Rate this article
    star-empty
    star-empty
    star-empty
    star-empty
    star-empty
    Related
    Article

    Introducing PSC Interconnect and Global Access for MongoDB Atlas


    Aug 05, 2024 | 2 min read
    Article

    A Decisioning Framework for MongoDB $regex and $text vs Atlas Search


    May 30, 2023 | 5 min read
    Article

    AI Shop: The Power of LangChain, OpenAI, and MongoDB Atlas Working Together


    Sep 18, 2024 | 7 min read
    Tutorial

    The Atlas Stream Processing Set-up Guide for Kafka Connector Users


    Aug 23, 2024 | 15 min read
    Table of Contents