We're live-coding on Twitch! Join us!
Build search functionality with Laravel Scout and Vue.js

Build search functionality with Laravel Scout and Vue.js


Today, we are going to learn how to build a simple search functionality using Laravel Scout and Vue.js. If you don't know what Laravel Scout is, here is what the Laravel documentation says:

Laravel Scout provides a simple, driver based solution for adding full-text search to your Eloquent models. Using model observers, Scout will automatically keep your search indexes in sync with your Eloquent records.

It's an official package, not included in Laravel by default but you can still pull it in with Composer and use in your Laravel apps. It is shipped with Algolia driver, but you can swipe drivers easily as the documentation says:

Currently, Scout ships with an Algolia driver; however, writing custom drivers is simple and you are free to extend Scout with your own search implementations.

Before diving into the code, let's take a look at what we are going to be building:

Installing Laravel:

To install Laravel, open your terminal and cd into your folder and run this command:

composer create-project --prefer-dist laravel/laravel search

After executing the command, change your document root to point to the public folder and make sure directories within the storage and the bootstrap/cache directories are writable by your web server.

Last step in installing Laravel is generating an application key which is used to encrypt your user sessions and other data, and you can do that by running:

better.dev Get Started w/ JavaScript for free!
php artisan key:generate

If you open your website in the browser you should see this exact same page:

Database configuration:

Rename the .env.example file to .env and add your database name, user and password.

I will be using SQLite for this demo, feel free to use MySQL or any database management system you prefer.


If you don't specify a database name, Laravel assumes it is located in database/database.sqlite.

Models and migrations:

For this small app we will only be needing a Product model and a products table.

Go ahead and generate those two:

php artisan make:model Product -m

When passing the -m flag to the php artisan make:model command a migration will be generated for you. That's a cool trick to know!

These are the fields we are currently interested in:

class CreateProductsTable extends Migration
     * Run the migrations.
     * @return void
    public function up()
        Schema::create('products', function (Blueprint $table) {

     * Reverse the migrations.
     * @return void
    public function down()

Save your file and migrate your tables: php artisan migrate

Dummy data:

Using Laravel Model Factories we will be generating some fake data to test with. add these lines to database/factories/ModelFactory.php:

$factory->define(App\Product::class, function (Faker\Generator $faker) {
    return [
        'title' => $faker->sentence(),
        'image' => 'http://loremflickr.com/400/300?random='.rand(1, 100),
        'price' => $faker->numberBetween(3, 100),
        'description' => $faker->paragraph(2)

Our model factory is ready, let's create some data. In your command line run: php artisan tinker and then: factory(App\Product::class, 100)->create();. You can create as many records as you want, 100 sounds perfect to me.

Routes and Controllers:

These two routes are all what we need for this app, so let's go ahead and create them.

  • GET : / the home route, this route will render our website home page.


Route::get('/', function () {
    return view('home');

You can use a controller, but I really don't think it's necessary for this route.

  • GET : api/search this route is responsible for handling search requests.


Route::get('/search', [
    'as' => 'api.search',
    'uses' => 'Api\[email protected]'

To create the SearchController class, simply run: php artisan make:controller Api\SearchController. If you didn't already know, when adding a namespace to your controller's class name Laravel will automatically create the folder for you and put the generated controller inside it.

class SearchController extends Controller
    public function search(Request $request)
        // we will be back to this soon!

Installing Laravel Scout:

Installing and configuring Laravel Scout is a breeze, you can pull the package using: composer require laravel/scout. When composer is done doing its thing add the ScoutServiceProvider to the providers array of your config/app.php configuration file:


Next, you should publish the Scout configuration using the vendor:publish Artisan command. This command will publish the scout.php configuration file to your config directory:

php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"

Finally, add the Laravel\Scout\Searchable trait to the Product to make it searchable:


namespace App;

use Laravel\Scout\Searchable;
use Illuminate\Database\Eloquent\Model;

class Product extends Model
    use Searchable;

Importing data to Algolia

Now that Laravel Scout is installed and ready, it's time to install the Algolia driver

composer require algolia/algoliasearch-client-php

all we need to do now is to import the data we generated earlier to Algolia, You can create a free account ( No credit card is required ) if you want to follow up with this tutorial.

When you are done creating your account browse to https://www.algolia.com/api-keys and copy your Application ID and Admin API Key and put them in your .env file like this:


In your terminal run

php artisan scout:import "App\Product"

If you did everything correctly, you should see this message, which means that all your products table data got copied to Algolia's servers.

You can verify that by browsing to the Indices page in your account.

Writing the search method:

Back to the search method in app\Http\Controllers\Api\SearchController.php.

     * Search the products table.
     * @param  Request $request
     * @return mixed
    public function search(Request $request)
        // First we define the error message we are going to show if no keywords
        // existed or if no results found.
        $error = ['error' => 'No results found, please try with different keywords.'];

        // Making sure the user entered a keyword.
        if($request->has('q')) {

            // Using the Laravel Scout syntax to search the products table.
            $posts = Product::search($request->get('q'))->get();

            // If there are results return them, if none, return the error message.
            return $posts->count() ? $posts : $error;


        // Return the error message if no keywords existed
        return $error;

Don't forget to import the Product model use App\Product; Now, browsing to http://example.com/api/search?q=code should return a JSON representation of your data.

The frontend setup:

We won't focus too much on the design in this tutorial, so here is the template we will be working with, copy/past it in your resources/home.blade.php file.

<!DOCTYPE html>
<html lang="en">
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
        <title>Search with Laravel Scout and Vue.js!</title>
        <div class="container">
            <div class="well well-sm">
                <div class="form-group">
                    <div class="input-group input-group-md">
                        <div class="icon-addon addon-md">
                            <input type="text" placeholder="What are you looking for?" class="form-control">
                        <span class="input-group-btn">
                            <button class="btn btn-default" type="button">Search!</button>
            <div id="products" class="row list-group">

You can see that all I have done is setup some boilerplate and import Twitter Bootstrap, you shouldn't be using the CDN for your real life apps, but since this is just a quick demo it's fine.

Importing Vue.js and vue-resource:

Add these lines before the body closing tag </body>:

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-resource/1.0.1/vue-resource.min.js"></script>
<script src="/js/app.js"></script>

Again, you should use a dependency manager for these kinds of things but it's beyond the scope of this tutorial so it is fine for now.

Next step is creating the app.js file inside public/js/ folder ( delete it if does already exist ) and this is what it will hold for now:

new Vue({
    el: 'body',

What this means is we are binding the Vue instance to the HTML body tag making our JavaScript only recognized between the HTML body tags.

Vue.js data and bindings:

To capture what the user wrote in the search input, display the results, show the error message or display the searching button we will need to add these lines just under the el property:

data: {
    products: [],
    loading: false,
    error: false,
    query: ''

This will force us to change our HTML a little bit, here is what we need to do:

  1. Add the v-model attribute to the search input to bind it to the query property in our data object
<input type="text" placeholder="What are you looking for?" class="form-control" v-model="query">
  1. Display the Searching... button when an action is being performed and show the normal search button if not.
<button class="btn btn-default" type="button" v-if="!loading">Search!</button>
<button class="btn btn-default" type="button" disabled="disabled" v-if="loading">Searching...</button>
  1. Same as above for the error message, we will add this under the closing tag of the div with the class of "well well-sm"
<div class="alert alert-danger" role="alert" v-if="error">
    <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
    @{{ error }}

To display data with Vue.js we use the moustache syntax {{ }}, but since Laravel is using the same syntax we need to tell not to parse it by adding the @ at its beginning.

Fetching and displaying data:

The remaining and most important step is to fetch the data. We will start by attaching an event listener to our search button and we do that by adding the @click attribute to it:

<button class="btn btn-default" type="button" @click="search()" v-if="!loading">Search!</button>

Let's create the search function and insert it under the data object:

methods: {
    search: function() {
        // Clear the error message.
        this.error = '';
        // Empty the products array so we can fill it with the new products.
        this.products = [];
        // Set the loading property to true, this will display the "Searching..." button.
        this.loading = true;

        // Making a get request to our API and passing the query to it.
        this.$http.get('/api/search?q=' + this.query).then((response) => {
            // If there was an error set the error message, if not fill the products array.
            response.body.error ? this.error = response.body.error : this.products = response.body;
            // The request is finished, change the loading to false again.
            this.loading = false;
            // Clear the query.
            this.query = '';

Finally, the HTML for the product, should go bellow the div with the ID of products.

<div class="item col-xs-4 col-lg-4" v-for="product in products">
    <div class="thumbnail">
        <img class="group list-group-image" :src="product.image" alt="@{{ product.title }}" />
        <div class="caption">
            <h4 class="group inner list-group-item-heading">@{{ product.title }}</h4>
            <p class="group inner list-group-item-text">@{{ product.description }}</p>
            <div class="row">
                <div class="col-xs-12 col-md-6">
                    <p class="lead">[email protected]{{ product.price }}</p>
                <div class="col-xs-12 col-md-6">
                    <a class="btn btn-success" href="#">Add to cart</a>

What we did there is we created a loop using the v-for directive and displayed the data just like we did before using the moustache syntax.

And with that, you should have this same exact website running in your server:

Final word:

If you reached this part, it means you completed the tutorial and you have built or ready to build your own search system. The code source of this tutorial is available here. If you have any problems or questions, please feel free to write a comment down in the comments section and I will be happy to help.

Like this article? Follow @rashidlaasri on Twitter