This tutorial is out of date and no longer maintained.
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 it 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:
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.
The last step in installing Laravel is generating an application key that is used to encrypt your user sessions and other data, and you can do that by running:
- php artisan key:generate
If you open your website in the browser you should see this exact same page:
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.
DB_CONNECTION=sqlite
If you don’t specify a database name, Laravel assumes it is located in database/database.sqlite
.
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) {
$table->increments('id');
$table->string('title');
$table->string('image');
$table->integer('price');
$table->text('description');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('products');
}
}
Save your file and migrate your tables:
- php artisan migrate
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.
These two routes are all that we need for this app, so let’s go ahead and create them.
/
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.
api/search
this route is responsible for handling search requests.Route::get('/search', [
'as' => 'api.search',
'uses' => 'Api\SearchController@search'
]);
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 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:
Laravel\Scout\ScoutServiceProvider::class,
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:
<?php
namespace App;
use Laravel\Scout\Searchable;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
use Searchable;
}
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:
ALGOLIA_APP_ID=ALGOLIA_APP_ID
ALGOLIA_SECRET=ALGOLIA_SECRET
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.
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.
We won’t focus too much on the design in this tutorial, so here is the template we will be working with, copy/paste it in your resources/home.blade.php
file.
<!DOCTYPE html>
<html lang="en">
<head>
<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>
</head>
<body>
<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">
</div>
<span class="input-group-btn">
<button class="btn btn-default" type="button">Search!</button>
</span>
</div>
</div>
</div>
<div id="products" class="row list-group">
</div>
</div>
</body>
</html>
You can see that all I have done is set up 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.
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.
The 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.
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:
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">
<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>
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 }}
</div>
To display data with Vue.js we use the Mustache syntax {{ }}
, but since Laravel is using the same syntax we need to tell not to parse it by adding the @
at its beginning.
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">$@{{ product.price }}</p>
</div>
<div class="col-xs-12 col-md-6">
<a class="btn btn-success" href="#">Add to cart</a>
</div>
</div>
</div>
</div>
</div>
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 Mustache syntax.
And with that, you should have this same exact website running on your server:
If you reached this part, it means you completed the tutorial and you have built or are 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.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!