Every two weeks I send out a newsletter containing lots of interesting stuff for the modern PHP developer. You can expect quick tips, links to interesting tutorials, opinions and packages. Want to learn the cool stuff? Then sign up now!

Integrating Algolia in a Laravel application

A few days ago Spatie, the company where I work, launched a new website: vrijwilligerswerk.be. Translated to english it roughly comes down to workforvolunteers.be. On the website various organizations can post their jobs that can be filled by a volunteer. Volunteers have a very user-friendly way to search for job. They can also opt in to weekly mails with new jobs that match their search queries. In this post I’ll give some background on how the application was built and highlight some of the technologies used.

When starting out with the project several months ago we decided performance and ease of use were essential in delevering a good result. You don’t want a potential volunteer to wade through complex forms and waiting for results. Our design team came up with a beautiful and easy to use search and results page. Here’s what it looks like:

Screen Shot 2016-02-27 at 20.41.03

As soon as you start typing a query or alter one of the criteria search results will be displayed immediately. You won’t see a spinner or something similar.

Screen Shot 2016-02-27 at 20.43.53
Take it for a spin yourself here.

Let’s get technical. The site runs on a Digital Ocean droplet. Nginx, PHP 7 and http2 are used. The site itself is built with Laravel 5.2. Like in almost all our projects we used our Blender template as a starting point.

When a new job gets submitted (either by an admin in the admin section or an organization when it is logged in), the job is sent to Algolia. For those who are not in the know: Algolia is a hosted search engine with a beautiful API. If you want to know more about them just visit their site or watch the video’s Jeffrey Way made on Laracasts about the service.

To send a job to Algolia our homebrew searchindex package is used. The package provides a Spatie\SearchIndex\Searchable-interface. It expects you to implement one method: getSearchableBody that’ll determine which data will get sent to Algolia.

//in the Job model

public function getSearchableBody() : array
    return [
        'id' => $this->id,

        'name' => $this->name,
        'profile' => $this->profile,
        'description' => $this->description,

        'address' => $this->address,
        'city_id' => $this->city->id,
        'postal' => $this->city->postal,
        'city' => $this->city->name,
        'lat' => $this->getPoint()->lat,
        'lng' => $this->getPoint()->lng,
        'listHtml' => (string)view('front.search._partials.job', ['job' => $this]),


See that listHtml-key? That contains the html that’ll be rendered on the search result page. More on that later.

Our Laravel app works event based. This is the code that will be performed when a job was updated (regardless of wheter an admin or an organization updated the job).

public function whenJobWasUpdated(JobWasUpdated $event)
    $job = $event->job;

        ? $this->index->upsertToIndex($job)
        : $this->index->removeFromIndex('job', $job->id);

Now that you know how jobs are being stored in the search index let’s talk about how we can search them. The search page is built with React. There are two big components on the page: the search form and the results section. Explaining how React works is out of scope for this article, if you want to learn more about that watch this presentation by Frank De Jonge at last year’s Laracon.

Whenever you click an option in the form or type a query the state of the form is changed. Whenever the state of the form changes, we’ll convert the state to an AlgoliaQuery object.

let algoliaQuery = new JobQuery()


The query is then passed to the searchIndex. The searchIndex is the code snippet above is just a simple wrapper around the official Algolia JavaScript client:

import algoliasearch from 'algoliasearch';

class SearchIndex {

        let client = algoliasearch(config.applicationId, config.apiKey);

        this.index = client.initIndex(config.indexName);

        return this.index.search(query.getSearchText(), query.getParameters());
export default SearchIndex;

When an answer rolls in from Algolia (which often takes no longer than just a few milliseconds) we’ll update the SearchResults store. This will update the page: the search results are now displayed.

Summarized this means that when a volunteer performs a query all magic happens clientside, there is not a single query being executed on the server. Remember that listHtml-key that we sent to Algolia? We simply display that piece of html on the searchresults page.

When a volunteer saves a search query we will, once a week, send that volunteer the new jobs that match the criteria of the query. The jobs in that mail are also determined by the Algolia index.

To wrap up I’ll highlight of the packages that were used:

  • algolia/algoliasearch-client-php: the official Algolia PHP client.
  • laracasts/PHP-Vars-To-Js-Transformer: this package provides an easy way to pass data from the server to JavaScript.
  • spatie/laravel-fractal: various pages on the website need data stored in the database. This package transforms the models to the format that is needed in JavaScript
  • watson/sitemap: on the site there isn’t a list with all the jobs. You can only find jobs when using the searchform. So a searchcrawler can’t find all pages. For those crawlers we build up a sitemap.
  • spatie/laravel-url-signer: In the new jobs mail digest that we send out weekly a volunteer can click unsubscribe links.
  • propaganistas/laravel-phone: an organization must put in a telephone number. This package makes it easy to validate that value

You know what? Here’s the entire require section of the composer.json file. I hope you’ll find something that can be useful in your project too.

"require": {
  "algolia/algoliasearch-client-php": "^1.6",
  "barryvdh/laravel-debugbar": "^2.1",
  "barryvdh/laravel-ide-helper": "~2.0",
  "bugsnag/bugsnag-laravel": "~1.4",
  "davejamesmiller/laravel-breadcrumbs": "~3.0",
  "dimsav/laravel-translatable": "~5.0",
  "doctrine/dbal": "^2.5",
  "filp/whoops": "^2.0",
  "fzaninotto/faker": "~1.4",
  "guzzlehttp/guzzle": "~6.0",
  "illuminate/html": "~5.0",
  "jenssegers/date": "^3.0",
  "laracasts/flash": "~1.3",
  "laracasts/presenter": "0.2.*",
  "laracasts/testdummy": "~2.3",
  "laracasts/utilities": "^2.1",
  "laravel/framework": "5.2.*",
  "league/flysystem-aws-s3-v2": "~1.0",
  "maatwebsite/excel": "^2.0.0",
  "maknz/slack": "~1.5",
  "myclabs/php-enum": "^1.4",
  "pda/pheanstalk": "~3.0",
  "phpseclib/phpseclib": "0.3.*",
  "propaganistas/laravel-phone": "^2.6",
  "spatie/activitylog": "~2.0",
  "spatie/array-functions": "^1.1",
  "spatie/eloquent-sortable": "~1.0",
  "spatie/geocoder": "^2.1",
  "spatie/integration": "^2.1.0",
  "spatie/laravel-analytics": "~1.0",
  "spatie/laravel-backup": "~2.4",
  "spatie/laravel-googletagmanager": "^2.0",
  "spatie/laravel-medialibrary": "^3.1.1",
  "spatie/laravel-or-abort": "^1.0",
  "spatie/laravel-paginateroute": "^1.0",
  "spatie/laravel-tail": "1.*",
  "spatie/laravel-url-signer": "^1.0",
  "spatie/searchindex": "^3.1.1",
  "spatie/seeders": "^3.0",
  "spatie/string": "^2.0",
  "spatie/laravel-authorize": "^1.0",
  "spatie/laravel-failed-job-monitor": "^1.0"
  "spatie/laravel-fractal": "^1.7",
  "spatie/laravel-newsletter": "^2.2.0",
  "spatie/laravel-partialcache": "^1.1",
  "spatie/laravel-permission": "^1.3",
  "spatie/laravel-robots-middleware": "^1.0",
  "spatie/laravel-sluggable": "^1.0",
  "vespakoen/menu": "~3.0",
  "watson/sitemap": "^2.0",

Our entire team learned quite a lot while working on this project. Though we released some other sites which leverage React in the meantime, we used it first on this project. We’re quite happy with the framework and will probably stick with it for a while. It was also the first time we used Algolia and that was a good experience as well. We still think Elasticsearch is awesome, but whenever we’ll need a client side API we’ll probably favor Algolia.

If you enjoyed reading this, here’s another technical post on how we built a Laravel webshop.

Small disclaimer: I’m not affiliated in any way with Algolia.

Freek Van der Herten is a partner and developer at Spatie, an Antwerp based company that specializes in creating web apps with Laravel. After hours he writes about modern PHP and Laravel on this blog. When not coding he’s probably rehearsing with his kraut rock band. He loves waffles and butterflies.
  • Great article Freek. Thanks for sharing!
    Would you also mind sharing that Digital Ocean droplet size this is? I think that will also be interesting to know.

    • Sure thing! Right now it’s a $20 droplet. Because executing a query on search page doesn’t impact the server at all, we might downscale the server to a $5 droplet in a few weeks .

      • Thanks for sharing Freek. Good argument. I find myself deploying a droplet with overcapacity all the time. Downscaling on DO is not that simple. You need to deploy a new server and copy everything over. Not a big deal, but it takes too much time.

        • You can downscale if you spin up a $5 Droplet and then dynamically scale it up. Everything but the diskspace will be upgraded. If you do it this way you can downscale without having to copy everything over to a new droplet.

  • bjrnblm

    Hi Freek, looks very nice!

    Are you guys running this on an Algolia Starter plan or a bigger plan? Could you share some (very) rough numbers about usage so far? Algolia looks like a very nice product to use, but I wonder how quickly it gets expensive to use.

    • Very roughly: we’re seeing 5 000 to 10 000 queries a day and we’re using the starting plan.

      • bjrnblm

        Thanks 🙂