Caching API Endpoints with Laravel

Dan Pastori avatar
Dan Pastori September 27th, 2022

As your API begins to grow, you will want to start looking into some performance optimizations. Laravel provides a variety of different options when it comes to caching endpoints, such as Redis, Memcached, DynamoDB, and even simpler file caches. By default, Laravel is set to cache in a file.

Since Laravel is facade driven, basic caches don’t matter from a programming perspective. The method to actually cache data is the same and will use what you have configured for your environment. However, you will get better performance using a high-availability service like Redis or Memcached as your app grows.

Why Cache API Endpoints?

Speed and performance. That’s why you’d want to cache an API endpoint. Say you have a database with thousands or millions of records. Every time you hit an endpoint to load data, you are querying the database to load the records you need. Throw a bunch of users on your system and you will get massive performance issues. If you cache pre-calculated data and update the data in your cache when it changes, you will have a much better performance!

You can get infinitely complex with how much you cache, but try to take the low hanging fruit first. By that, I mean say you have an endpoint that rarely gets updated, but has a lot of requests and a lot of data computation. Those are the perfect places to begin where you will see the biggest performance increase.

For this tutorial, I’ll be going through a real life example of an app I’m working on called Amplitude Hosting that works with AmplitudeJS. Amplitude Hosting allows you to create a config file to load into your Amplitude player through a URL. The endpoint can get complicated to compute, but doesn’t change very often. We want to load it through a URL structured like /api/v1/configs/{key}. Our end game being to return a JSON object with our config pre-computed and up-to-date resource from the cache. All without touching our database. Let’s get started!

Caching a Resource Endpoint

The first step is to save your resource to a cache, otherwise you will have nothing to return! Let’s pretend we just persisted our Amplitude Config to the database. After you persist your resource to the database, then it’s time to cache it.

As I mentioned previously, we will be using the Illuminate\Support\Facades\Cache facade provided by Laravel to work with our cache. This facade will work with whatever caching system we have in place.

So in our controller or service, include the following in your use statements:

use Illuminate\Support\Facades\Cache;

I like to keep my controllers small, so I created a service that saves our config. Right now, it looks like:

<?php

namespace App\Services\Configs;

use App\Models\Config;
use Illuminate\Support\Facades\Cache;

class SaveConfig
{
    private $data;

    public function __construct( $data )
    {
        $this->data = $data;
    }

    public function persist()
    {
        $config = $this->saveToDatabase();

        return $config;
    }

    private function saveToDatabase()
    {
        // Save config to database
    }
}

Let’s add our cache functionality now:

<?php

namespace App\Services\Configs;

use App\Models\Config;
use Illuminate\Support\Facades\Cache;

class SaveConfig
{
    private $data;

    public function __construct( $data )
    {
        $this->data = $data;
    }

    public function persist()
    {
        $config = $this->saveToDatabase();
        $this->persistInCache( $config );

        return $config;
    }

    private function saveToDatabase()
    {
        // Save config to database
    }

    private function persistInCache( $config )
    {
        Cache::forever('config_'.$config->unique_id, $config);
    }

}

Two things to note. First, we persist to the cache after the resource has been saved to the database. This way we can return the resource exactly how it would appear from our database, except without the query.

Second, in our persistInCache() method, we call the Cache::forever() method on the Cache facade. The Cache::forever() method accepts 2 parameters. The first parameter is the unique id of the key within the cache. In this case it’s config_{unique_config_id}. This way we know how to reference the key when returning from the cache.

The second parameter is the $config itself. This is the data we will return from the cache. Since we used the Cache::forever() method, the cache will remain until it’s explicitly deleted. This works perfect for our use case.

There are actually a few methods you can call depending on your use case. Cache::put() allows you to define a key and a value but also accepts a third parameter of expiration_time . The expiration_time can be either the amount of seconds the value should live in the cache or the DateTime instance of when the cache should expire.

The other option is Cache::add() which is the same as Cache::put()except only adds if the value does not exist. You can read more about the methods here. Now that we have our Config stored in the cache, let’s return the value!

Returning Data from a Cached Endpoint with Laravel

The simplest way to return a value from the cache is to use the Cache::get() method. The Cache::get() method accepts the key of your cached item and returns the value. So in our case, on our /api/v1/configs/{key} endpoint, we would return the value at Cache::get('config_{key}'). Our service to load the config from the cache could look like this:

<?php

namespace App\Services\Configs;

use App\Models\Config;
use Illuminate\Support\Facades\Cache;

class LoadCachedConfig
{
    private $key;

    public function __construct( $key )
    {
        $this->key = $key;
    }

    public function load()
    {
        return Cache::get('config_'.$this->key);
    }
}

The service accepts the key of the config and loads it dynamically from the cache without touching our database (we did not update the typehint for the $config to load from the $key variable or it would have hit our database).

This is great, but there are instances where your cache is out of sync like after a reboot, or you need to rebuild and the value may not exist. So I like to return my cached values like this:

<?php

namespace App\Services\Configs;

use App\Models\Config;
use Illuminate\Support\Facades\Cache;

class LoadCachedConfig
{
    private $key;

    public function __construct( $key )
    {
        $this->key = $key;
    }

    public function load()
    {
        if( Cache::has('config_'.$this->key) ){
            return Cache::get('config_'.$this->key);
        }else{
            return Cache::rememberForever('config_'.$this->key, function () {
                $config = Config::where('unique_id', '=', $this->key)
                            ->first();
                
                return $config;
            });
        }
    }
}

It looks slightly more complex, but it’s actually fairly simple and elegant, let’s dive in!

First, we check to see if the Cache::has() a config with the key specified. If it does, we simply return the config at that key. Second, if there is no config at the key, we run the Cache::rememberForever() method, passing the key and the config function. This is an EXTREMELY helpful method. The first parameter should look familiar since it’s the key that we want to cache the value at. The second parameter is a callback function that loads the value. In this case, it’s our Config resource. After we load the value, set the value at the key and return what was loaded from the database. Yes, it’s a database call, but it only happens once so any sub-sequent requests will be cached appropriately! This way you cache is never out of sync! Unless, you update the value of the config, in that case, see the next section.

Updating a Cached Endpoint with Laravel

In our scenario, configs aren’t updated often, but they are updated. When they are updated, we need to “re-prime” the cache with the latest and greatest data so it’s ready to go. It’s pretty simple. After you persist the updates to the database you can prime the cache with the same method as saving the cache. It will just overwrite the value:

Cache::forever('config_'.$this->config->unique_id, $this->config );

Now when we call our endpoint, /api/v1/configs/{key}, we will get the latest version of the config without calling from the database. This will speed up responses dramatically.

Deleting a Cached Endpoint in Laravel

Say you changed your structure and no longer want to save a cached config. Or the user deletes the config and you don’t want it cached any more. All you have to do is call the Cache::forget('config_{key}') method wherever you want to delete from the cache. This will remove the the value from the cache and you are good to go!

Conclusion

Some other benefits of caching endpoints with Laravel is it’s so easy to switch caching services. Just swap out your config and your cache will go somewhere else without updating the code. You might have to “re-prime” your new cache but at least you won’t have to re-factor a ton of code.

You can also cache values that may not be endpoints, but take a long time to compute. For example, we are working on “Net Worth Over Time” and monthly stats with Financial Freedom. Those can take a long time to compute if you have a ton of transactions. However, we can compute them when updated and save them to a cache to load them instantly when needed. You can even cache them after a queue process so your app is smooth and responsive!

Hopefully this helped shed some light on how to speed up API endpoints with Laravel and Caching. Any questions, reach out on Twitter (@danpastori). Also, check out our book on API Development. We have a ton of tricks and real world code available and the API is 100% Laravel!

Support future content

The Ultimate Guide to Building APIs and Single-Page Applications with Laravel + VueJS + Capacitor book cover.

Psst... any earnings that we make off of our book is being reinvested to bringing you more content. If you like what you read, consider getting our book or get sweet perks by becoming a sponsor.

Written By Dan

Dan Pastori avatar Dan Pastori

Builder, creator, and maker. Dan Pastori is a Laravel certified developer with over 10 years experience in full stack development. When you aren't finding Dan exploring new techniques in programming, catch him at the beach or hiking in the National Parks.

Like this? Subscribe

We're privacy advocates. We will never spam you and we only want to send you emails that you actually want to receive. One-click unsubscribes are instantly honored.