Deploy Laravel and Memcache on Render.com: The Complete Guide

Do you want to deploy a Laravel 10 application on Render and boost its performance with Memcache? In this tutorial, you’ll create a simple Laravel task list app, deploy it to Render, and use Memcache to speed it up.

Memcache is an in-memory data storage system, a tool that significantly enhances the efficiency and scalability of web applications. Consider integrating Memcache if your pages exhibit sluggish load times or your application faces scaling challenges. Regardless of your app’s size, Memcache has the potential to quicken page loads and help future-proof your app.

Render.com is a cloud service platform that provides developers with tools and services to host and deploy a wide range of web applications, static sites, and background workers.

Laravel is a robust, open-source PHP framework that provides a simple, elegant syntax and tools to build full-featured web applications.

Outline

Prerequisites

  • Familiarity with PHP and, ideally, Laravel.
  • A Render account.
  • A GitHub account and Git installed on your local machine. To deploy to Render, you must push code to a remote code repository. Render supports GitHub and GitLab.
  • PHP, Composer, and Git installed on your computer.

Initialize a Laravel project

To start, create a new Laravel project. In your terminal, run the following command:

composer create-project laravel/laravel laravel_memcache

That command will use the latest version of Laravel, which at the time of writing v10.2.5.

# output
Creating a "laravel/laravel" project at "./laravel_memcache"
Info from https://repo.packagist.org:
Installing laravel/laravel (v10.2.5)
  - Downloading laravel/laravel (v10.2.5)
  - Installing laravel/laravel (v10.2.5): Extracting archive
...

Next, change to the directory just created:

cd laravel_memcache

Then, install Laravel’s NPM dependencies:

npm install

And start Laravel’s development server using the Artisan CLI serve command:

php artisan serve

Once you have started the development server, you can view the Laravel skeleton app in your web browser at http://localhost:8000.

Create a Laravel task list app

In this step, you’ll add a task list to the app, enabling users to view, add, and delete tasks. To accomplish this, you need to:

  • Set up a local database.
  • Create the Task model.
  • Create the view and controller logic to add, view, and delete tasks.

Set up a local database (Optional)

To test your app locally, we recommend using an SQLite database. Install SQLite on your computer if necessary.

Add the database by creating the file database/database.sqlite.

To use this connection locally, update DB_CONNECTION and remove DB_DATABASE in your app’s .env file:

# .env
# ...
DB_CONNECTION=sqlite
# ...
# DB_DATABASE=laravel
# ...

You remove the DB_DATABASE variable because the default SQLite config looks for it before defaulting to the desired database.sqlite database. If you look at the SQLite configuration in config/database.php, you’ll find 'database' => env('DB_DATABASE', database_path('database.sqlite')).

Create the Task model

To create and store tasks, we need to do three things:

First, create a migration that will create the tasks table:

php artisan make:migration create_tasks_table --create=tasks

That command creates a migration named create_tasks_table.

Tasks should have names, so add a name field to the tasks table schema in the newly created database/migrations/<date>_create_tasks_table.php file. Note the date in your filename will be generated depending on when you run the command. It will look something like 2023_07_12_081805_create_tasks_table.php:

// database/migrations/<date>_create_tasks_table.php
// ...
Schema::create('tasks', function (Blueprint $table) {
    $table->id();
    $table->string('name'); // Add this line
    $table->timestamps();
});
// ...

Second, create a Task model to easily interact with the tasks table from our code:

php artisan make:model Task

This command creates an empty Task model in app/Task.php. Laravel automatically infers the Task model fields from the migration you created in the previous step.

Third, run the migrations:

php artisan migrate

Add a view for the task list

To see the tasks stored in the database, create a view that displays all tasks.

Create the file resources/views/layouts/app.blade.php, open it in your editor, and add the following:

<!-- resources/views/layouts/app.blade.php -->
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>MemCachier Laravel Tutorial</title>
        <meta name="viewport" content="width=device-width, initial-scale=1">

        @vite('resources/css/app.css')
    </head>

    <body>
        @yield('content')

        @vite('resources/js/app.js')
    </body>
</html>

Note we’ll include the app.css and app.js files generated with a new Laravel project, though we won’t add any CSS or JavaScript in this guide.

Now, create the task list view as a child view of the app.blade.php layout:

Create the file resources/views/tasks.blade.php, open it in your editor, and add the following:

<!-- resources/views/tasks.blade.php -->
@extends('layouts.app')

@section('content')
    <!-- TODO: New Task Card -->

    <!-- Current Tasks -->
    @if (count($tasks) > 0)
        <h2>Current Tasks</h2>
        <ul>
            @foreach ($tasks as $task)
                <li>
                    <!-- Task Name -->
                    <div>{{ $task->name }}</div>
                    <!-- TODO Delete Button -->
                </li>
            @endforeach
        </ul>
    @endif

@endsection

Ignore the TODOs for now. You’ll fill them out later.

To access this view, connect it to the root route, GET /, in routes/web.php:

// routes/web.php
use Illuminate\Support\Facades\Route;
use App\Models\Task;

// ...

// Show Tasks
Route::get('/', function () {
    $tasks = Task::orderBy('created_at', 'asc')->get();

    return view('tasks', [
        'tasks' => $tasks
    ]);
});

With these changes, you import the Task model. Then, in the root route, you get tasks ordered by their creation date and pass that task data to the view.

Enable task creation

Next, add the ability to create tasks. Open up resources/views/tasks.blade.php and replace the <!-- TODO: New Task Card --> comment with the following:

<!-- resources/views/tasks.blade.php -->
<!-- ... -->

<!-- New Task Card -->
<div>
    <h2>New Task</h2>

    <!-- New Task Form -->
    <form action="{{ url('tasks') }}" method="POST">
        {{ csrf_field() }}

        <label>
            Task Name: <input type="text" name="name" placeholder="e.g. Wash the dishes" required>
        </label>

        <button>Add Task</button>
    </form>
</div>

<!-- Current Tasks -->
<!-- ... -->

For security, validating user input, such as when a task is added, is essential. However, we’ll forego validating data to concentrate on the primary objectives of this tutorial: deploying Laravel on Render and caching with Memcache.

Let’s add these new views to routes/web.php:

// routes/web.php
// ...

use Illuminate\Support\Facades\Route;
use App\Models\Task;
use Illuminate\Http\Request;

// ...

// Show Tasks
// ...

// Add New Task
Route::post('/tasks', function (Request $request) {
    // Create task
    $task = new Task;
    $task->name = $request->name;
    $task->save();

    return redirect('/');
});

Enable task deletion

To complete your task list functionality, you’ll add the ability to delete a task. Add a Delete button to each item in the task list to delete a task. In resources/views/tasks.blade.php, replace the <!-- TODO Delete Button --> comment with the following changes:

<!-- resources/views/tasks.blade.php -->

<!-- ... -->

<!-- Delete Button -->
<form action="{{ url('tasks/'.$task->id) }}" method="POST">
    {{ csrf_field() }}
    {{ method_field('DELETE') }}

    <button>Delete</button>
</form>

<!-- ... -->

Then, wire this functionality to the appropriate route in routes/web.php:

// routes/web.php
// ...

// Show Tasks & Add New Task
// ...

// Delete Task
Route::delete('/tasks/{task}', function (Task $task) {
    $task->delete();

    return redirect('/');
});

If you have set up your app locally, it’s time to see how it works in the browser.

Now that you include assets with the @vite directive, you must run the Vite development server for the Laravel development server to work. Open a new terminal tab or window and start the Vite development server to build and live reload your assets:

npm run dev

You can now start a web server if you have set up the app locally. In your terminal, run the following:

php artisan serve

If you don’t run npm run dev, you’ll get an error Vite manifest not found at: <your_local_path>/public/build/manifest.json. If you get the same error in production, see the step Create a Dockerfile to run Nginx and PHP.

Finally, view the app at localhost:8000. You should now be able to add, view, and delete tasks.

Configure your app for Render

To prepare your Laravel app to be deployed to Render, you’ll do the following:

Create a Dockerfile to run Nginx and PHP

At the time of writing, Render Web Services have native support for Node.js, Python, Ruby, Elixir, Go, and Rust, but not PHP. Fortunately, Render supports Docker, allowing you to create a Dockerfile to deploy anything you need.

You’ll create a Dockerfile to provision a Render web service that runs Nginx with PHP, enabling you to run a Laravel app.

Create a file named Dockerfile, open it up in your code editor, and add the following:

# Dockerfile
# Use base image for container
FROM richarvey/nginx-php-fpm:3.1.6

# Copy all application code into your Docker container
COPY . .

RUN apk update

# Install the `npm` package
RUN apk add --no-cache npm

# Install NPM dependencies
RUN npm install

# Build Vite assets
RUN npm run build

CMD ["/start.sh"]

Later, once your app is deployed, if you get the error Vite manifest not found at: /var/www/html/public/build/manifest.json, it’s most likely because npm run build is not being called during the deploy process, and so your Vite assets and Vite manifest.json file are not being built. Refer to this step and ensure npm install and npm run build are being called.

Similarly, if you get the error sh: vite: not found, it’s likely npm install was not called, meaning vite was not installed.

Note you use the apk package manager as the richarvey/nginx-php-fpm base image runs on Alpine Linux.

Next, you’ll configure Nginx for your Docker container.

Configure Nginx

To add Nginx configuration to your Docker container, create the file conf/nginx/nginx-site.conf.

By default, the richarvey/nginx-php-fpm Docker image looks for configuration files in a conf folder. See the image Nginx config documentation.

# nginx-site.conf
server {
  # Render provisions and terminates SSL
  listen 80;

  # Make site accessible from http://localhost/
  server_name _;

  root /var/www/html/public;
  index index.html index.htm index.php;

  # Disable sendfile as per https://docs.vagrantup.com/v2/synced-folders/virtualbox.html
  sendfile off;

  # Add stdout logging
  error_log /dev/stdout info;
  access_log /dev/stdout;

  # Block access to sensitive information about git
  location /.git {
    deny all;
    return 403;
  }

  add_header X-Frame-Options "SAMEORIGIN";
  add_header X-XSS-Protection "1; mode=block";
  add_header X-Content-Type-Options "nosniff";

  charset utf-8;

  location / {
      try_files $uri $uri/ /index.php?$query_string;
  }

  location = /favicon.ico { access_log off; log_not_found off; }
  location = /robots.txt  { access_log off; log_not_found off; }

  error_page 404 /index.php;

  location ~* \.(jpg|jpeg|gif|png|css|js|ico|webp|tiff|ttf|svg)$ {
    expires 5d;
  }

  location ~ \.php$ {
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    fastcgi_pass unix:/var/run/php-fpm.sock;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param SCRIPT_NAME $fastcgi_script_name;
    include fastcgi_params;
  }

  # deny access to . files
  location ~ /\. {
    log_not_found off;
    deny all;
  }

  location ~ /\.(?!well-known).* {
    deny all;
  }
}

This Nginx configuration is taken from Render’s Laravel quickstart repo. The Nginx configuration is still accurate. However, their repo uses Laravel 5 and will not work with Laravel 10. Not to worry, though. We have you covered in this tutorial.

Create a deploy script

Create a file scripts/00-laravel-deploy.sh and add the following to it:

#!/usr/bin/env bash

echo "Running composer install..."
composer install --no-dev --working-dir=/var/www/html

echo "Running migrations..."
php artisan migrate --force

For the command php artisan migrate --force, the --force option will run the migrations without prompting for confirmation.

You could add any other commands you want to this file. For example, php artisan config:cache or php artisan route:cache.

In the upcoming step, Define your Render infrastructure as code, you will set the flag RUN_SCRIPTS to 1 to run this script once your container is ready.

Create a .dockerignore file

Next, create a .dockerignore file to avoid copying unnecessary files to your Docker container. Ignoring unnecessary files can make your Docker build faster. Open the file and add the following:

# .dockerignore
.DS_Store
.dockerignore
.editorconfig
.env
.env.backup
.env.example
.env.production
.git
.gitattributes
/.fleet
/.idea
/.phpunit.cache
/.vscode
Homestead.json
Homestead.yaml
Dockerfile
README.md
/auth.json
/node_modules
/npm-debug.log
/phpunit.result.cache
/public/build
/public/hot
/public/storage
/phpunit.xml
storage/app/*
storage/framework/cache/*
storage/framework/sessions/*
storage/framework/views/*
storage/logs/*
tests
/vendor
yarn-error.log

Force HTTPS on all assets served by Laravel

In this step, you’ll force HTTPS on all assets served by Laravel to avoid mixed content warnings in the browser.

Open up app/Providers/AppServiceProvider.php in your code editor. Import UrlGenerator and update the boot method as follows:

# app/Providers/AppServiceProvider.php
namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Routing\UrlGenerator;

class AppServiceProvider extends ServiceProvider
{
    // ...

    /**
     * Bootstrap any application services.
     */
    public function boot(UrlGenerator $url): void
    {
        if (env('APP_ENV') == 'production') {
            $url->forceScheme('https');
        }
    }
}

Next, you’ll write a Render Blueprint spec to define your Render infrastructure as code.

Define your Render infrastructure as code

Create a render.yaml Blueprint spec file in your project root directory. You’ll define a Docker Web Service and PostgreSQL Database in the spec.

# render.yaml
databases:
  - name: laravel_memcache_db
    plan: free
    databaseName: laravel_memcache_db
    user: laravel_memcache_db

services:
  - type: web
    plan: free
    name: laravel_memcache
    runtime: docker
    envVars:
      # Database
      - key: DATABASE_URL
        fromDatabase:
          name: laravel_memcache_db
          property: connectionString
      - key: DB_CONNECTION
        value: pgsql
      # Laravel
      - key: APP_KEY
        sync: false
      - key: APP_ENV
        value: production
      - key: APP_DEBUG
        value: false
      - key: LOG_CHANNEL
        value: stderr
      # Image-specific vars (richarvey/nginx-php-fpm)
      - key: WEBROOT
        value: /var/www/html/public
      - key: RUN_SCRIPTS
        value: 1
      - key: REAL_IP_HEADER
        value: 1
      - key: SKIP_COMPOSER
        value: 1

Note you’re specifying Render’s Free plan for both the database and web service, which is sufficient for this tutorial. The default plan is Starter.

The web service environment variable DATABASE_URL imports a Postgres connection string from the laravel_memcache_db database you defined.

The APP_KEY environment variable has the property sync: false, making it a Render placeholder environment variable. Placeholder environment variables are those which you want to exclude from your Blueprint spec and are not generated, such as external secrets. You will later be prompted to enter a generated APP_KEY value on the Render dashboard before your deployment is finalized.

Setting LOG_CHANNEL to stderr ensures logs will appear in the Logs tab of your Render Web Service dashboard.

With the flag RUN_SCRIPTS to 1, the scripts/00-laravel-deploy.sh script you created will run once your container is ready. Script files in the /scripts folder will be run in order of prefixed number. A file beginning with 00- would run first, 01- would run second, and so on. For more info, see the repository layout documentation for the richarvey/nginx-php-fpm image.

For explanations of the other richarvey/nginx-php-fpm image-specific environment variables, see the Available Configuration Parameters documentation.

See the Render Blueprint spec documentation for an explanation of all properties in your render.yaml file.

Create a remote code repository for your app

To deploy to Render, you need a remote code repository. In this step, you’ll commit your code to your app’s git repository and push it to GitHub.

Apps generated with the create-project CLI command come with a .gitignore file. So you can initialize a Git repo:

git init

Stage your code:

git add .

And commit:

git commit -m 'Initial commit: Create task list app'

Now your code is committed, you’ll push it to GitHub.

In your browser, log in to GitHub and create an empty repository called laravel_memcache. The repo can be public or private.

Back in the terminal, add your GitHub repo as a remote origin, replacing your_username with your actual GitHub username:

git remote add origin https://github.com/<your_username>/laravel_memcache.git

That command tells Git where to push your code to.

Rename the default branch:

git branch -M main

And push your code to GitHub:

git push -u origin main

Your app’s code is now on GitHub, ready to be deployed to Render.

Deploy Laravel to Render

Now that your code is on GitHub, deploying to Render is as simple as creating a new Blueprint Instance and connecting your GitHub repository.

Log in to Render and go to your Dashboard. Click New +, then click Blueprint.

Connect your GitHub account if you still need to do so. Then, find your repo in the Connect a repository section of the Create a new Blueprint Instance page and click Connect.

After clicking Connect you’ll be taken to a settings page. Choose a Blueprint Name. I’ll stick with laravel_memcache. Note the region you’re creating your app in. You’ll need it later.

Next, generate your own APP_KEY. In your terminal window, run the following command:

php artisan key:generate --show

Copy the generated value and set it as the APP_KEY environment variable value.

Finally, click Apply to begin deploying.

If the web service deployment fails and you receive a notice An error has occurred, click on the name of the web service to display its Events tab. You’ll see an event for the failed deployment. Click on deploy logs in the event to investigate.

When the deployment is live, go to your Render Dashboard, click on your Web Service, and finally click on its URL under the Web Service name to open it in your browser. The task list app should work as it does locally.

If you see a 502 Bad Gateway error in the browser, return to the step Configure Nginx and make sure the path to your Nginx configuration file is correct, conf/nginx/nginx-site.conf.

Next, you’ll implement caching.

Set up caching in Laravel

Setting up caching with Memcached in Laravel involves the following steps:

Create a Memcached-compatible cache

In this tutorial, you’ll create a free Memcached-compatible cache with MemCachier. For another option to use Memcached on Render, read our blog post to learn how to run Memcached as a Render Private Service.

MemCachier is a fully managed caching service that simplifies setting up and using Memcached in your web applications. Built for seamless integration with cloud platforms like Render, MemCachier offers developers a streamlined and hassle-free way to implement high-performance caching in their applications without worrying about the complexities of managing a cluster of Memcached servers.

To begin, create a new MemCachier cache. Choose Render as the provider. Choose the same region as your Render Web Service. Choose the Free plan. Finally, click CREATE CACHE.

Screenshot of the MemCachier create cache page, creating a Render.com cache

After creating your cache, you’ll find its configuration settings (Username, Password, Servers) on the CACHES dashboard.

Screenshot of the MemCachier caches dashboard with a Render.com cache

On your Render dashboard, select your Web Service, then select the Environment tab. Then, add your MemCachier Servers, Username, and Password config values, naming the environment variables MEMCACHIER_SERVERS, MEMCACHIER_USERNAME, and MEMCACHIER_PASSWORD, respectively.

Screenshot of Render.com MemCachier Environment Variables web service settings

Click Save Changes. Next, you’ll configure Laravel to use your cache.

Install the memcached PHP extension

Update your Dockerfile to install the memcached PHP extension and its required packages:

# Dockerfile
# ...

# Build Vite assets
RUN npm run build

# Install required packages
RUN apk add --no-cache autoconf g++ make libmemcached-dev

# Install the `memcached` PHP extension with SASL auth enabled
RUN pecl install --configureoptions 'enable-memcached-sasl="yes"' memcached

# Enable the `memcached` PHP extension
RUN docker-php-ext-enable memcached

CMD ["/start.sh"]

The libmemcached-dev package is installed because it depends on the memcached PHP extension. The packages autoconf, g++, and make are all required for the pecl install ... command to work. If they are missing, you’ll get the error messages Cannot find autoconf, no acceptable C compiler found in $PATHa, and sh: make: not found respectively.

The memcached PHP extension is installed with SASL support enabled because MemCachier caches require SASL (username and password) authentication.

Configure Laravel to use Memcached

Configure the cache by updating the memcached settings in config/cache.php. Set the CACHE_DRIVER to memcached and replace the memcached configuration in the stores array with that listed below:

// config/cache.php
//..

'default' => env('CACHE_DRIVER', 'memcached'),

// ...

'stores' => [
  // ...

  'memcached' => [
      'driver' => 'memcached',
      'persistent_id' => 'memcached_pool_id',
      'sasl' => [
          env('MEMCACHIER_USERNAME'),
          env('MEMCACHIER_PASSWORD'),
      ],
      'options' => [
          // some nicer default options
          // - nicer TCP options
          Memcached::OPT_TCP_NODELAY => TRUE,
          Memcached::OPT_NO_BLOCK => FALSE,
          // - timeouts
          Memcached::OPT_CONNECT_TIMEOUT => 2000,    // ms
          Memcached::OPT_POLL_TIMEOUT => 2000,       // ms
          Memcached::OPT_RECV_TIMEOUT => 750 * 1000, // us
          Memcached::OPT_SEND_TIMEOUT => 750 * 1000, // us
          // - better failover
          Memcached::OPT_DISTRIBUTION => Memcached::DISTRIBUTION_CONSISTENT,
          Memcached::OPT_LIBKETAMA_COMPATIBLE => TRUE,
          Memcached::OPT_RETRY_TIMEOUT => 2,
          Memcached::OPT_SERVER_FAILURE_LIMIT => 1,
          Memcached::OPT_AUTO_EJECT_HOSTS => TRUE,

      ],
      'servers' => array_map(function($s) {
          $parts = explode(":", $s);
          return [
              'host' => $parts[0],
              'port' => $parts[1],
              'weight' => 100,
          ];
        }, explode(",", env('MEMCACHIER_SERVERS', 'localhost:11211')))
  ],

  // ...
],

// 

These changes configure the Laravel caching engine with MemCachier, allowing you to use your Memcache in a few different ways:

  • Directly access the cache via get, set, delete, and so on
  • Cache results of functions with the rememberForever function
  • Use Memcache for session storage
  • Cache entire responses

Implement caching strategies in Laravel

In this section, you’ll implement several caching techniques in Laravel:

Cache expensive database queries

Memcache is often used to cache the results of expensive database queries. Of course, our simple task list does not have any resource-intensive queries, but let’s assume that fetching all the tasks from the database is a slow operation for this tutorial.

The rememberForever function makes it easy to add caching to Laravel. You provide two arguments for it:

  • A cache key
  • A function that queries your database and returns results

The rememberForever function looks up the key in your cache. If the key is present, its corresponding value is returned. Otherwise, the database function you provided is called. Whatever that function returns is stored in the cache with the corresponding key for future lookups.

The first time you call rememberForever, the resource-intensive database function is called, but every successive call to rememberForever obtains the value from the cache.

Use the rememberForever function to add caching to the task view controller in routes/web.php:

// routes/web.php
// ...
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;

// ...

// Show Tasks
Route::get('/', function () {
  $tasks = Cache::rememberForever('all_tasks', function () {
    return Task::orderBy('created_at', 'asc')->get();
  });

  return view('tasks', [
    'tasks' => $tasks
  ]);
});

As you might have noticed, we now have a problem if we add or remove a task. Because rememberForever fetches the task list from the cache, any changes to the database won’t be reflected in the task list. For this reason, whenever we change the tasks in the database, we need to invalidate the cache:

// routes/web.php
// Add New Task
Route::post('/tasks', function (Request $request) {
    // ...
    $task->save();

    Cache::forget('all_tasks');

    return redirect('/');
});

// Delete Task
Route::delete('/tasks/{task}', function (Task $task) {
  $task->delete();

  Cache::forget('all_tasks');

  return redirect('/');
});

Redeploy the app to Render with:

git add .
git commit -m 'Add expensive database query caching'
git push

View Memcache statistics

To help demystify Memcache caching operations, it’s invaluable to be able to visualize what’s going on under the hood.

Though very cumbersome, one way to do that is to telnet into a Memcached server and run the stats command to see changes as operations are performed on your cache.

With MemCachier, however, you get an analytics dashboard that displays your cache’s statistics to monitor performance and troubleshoot issues quickly and easily.

To open your MemCachier analytics dashboard, log in to your MemCachier account, click Caches, then click the Analytics button for your cache.

Screenshot of the MemCachier analytics dashboard

You can see that the first time you access the page, the get misses increases by one. This is because the first time rememberForever is called, the task list is not in the cache. The Set Cmds also increases because the task list is saved to the cache. If you refresh the page, the misses stay the same, but the get hits increases because the task list is served from the cache.

When you add a new task or delete a task, your misses will increase again because the cache was invalidated.

Using Memcache for session storage

Memcache works well for storing information for short-lived sessions that time out. However, because Memcache is a cache and therefore not persistent, long-lived sessions are better suited to permanent storage options, such as your database.

Changing the session store from file (default) to memcached is done by setting the SESSION_DRIVER config variable. To do so, go to your Web Service Environment tab, and add the variable SESSION_DRIVER with the value memcached.

Now, the next time you load your app, get misses will increase by an additional one, and your session will be cached. On subsequent page loads, get hits will increase by an additional one, giving you two get hits in total: one for the cached session and one for the cached tasks query.

Laravel session config can be found in config/session.php. Notice we have just set the driver option by setting that environment variable. You can read more in the Laravel HTTP Session documentation.

Caching entire responses

In Laravel, it’s easy to cache the entire responses using the laravel-responsecache package. Response caching in Laravel is similar to view caching in Ruby on Rails. By default, laravel-responsecache will cache all successful GET requests that return text-based content (such as HTML and JSON) for one week.

Even though your create task form includes a CSRF token, laravel-responsecache includes Replacers for replacing dynamic content in a cached response. By default, a CSRF replacer is included and works without additional configuration. Custom Replacers can also be written.

To use laravel-responsecache, first, install the package:

composer require spatie/laravel-responsecache

Next, install the provided middlewares \Spatie\ResponseCache\Middlewares\CacheResponse::class and \Spatie\ResponseCache\Middlewares\DoNotCacheResponse in the http kernel:

// app/Http/Kernel.php

// ...

protected $middlewareGroups = [
   'web' => [
       // ...
       \Spatie\ResponseCache\Middlewares\CacheResponse::class,
   ],

// ...

protected $middlewareAliases = [
   ...
   'doNotCacheResponse' => \Spatie\ResponseCache\Middlewares\DoNotCacheResponse::class,
];

And finally, set the RESPONSE_CACHE_DRIVER web service environment variable to memcached. Open up your Render Web Service dashboard, go to the Environment tab, and add the variable RESPONSE_CACHE_DRIVER with the value memcached.

Commit and deploy your code:

git add .
git commit -m 'Add response caching'
git push

Notice that tasks can no longer be added or deleted. This is because the cached response is not invalidated when its contents change. The response must be invalidated as when you cached the tasks query.

To invalidate or clear the cached response, make the following changes to routes/web.php:

// routes/web.php
...
use Illuminate\Support\Facades\Cache;
use Spatie\ResponseCache\Facades\ResponseCache;

...

// Add New Task
Route::post('/tasks', function (Request $request) {
    ...

    Cache::forget('all_tasks');
    ResponseCache::forget('/');

    return redirect('/');
});

// Delete Task
Route::delete('/tasks/{task}', function (Task $task) {
    ...

    Cache::forget('all_tasks');
    ResponseCache::forget('/');

    return redirect('/');
});

With those changes, you clear the cache for a specific URI, /. Several other options exist for invalidating the cache explained in the laravel-responsecache documentation.

Note, at this point, caching the tasks query (which you did earlier to simulate caching an expensive computation) doesn’t add any additional caching benefit, as response caching takes care of caching that query. However, you could imagine a view using data from multiple queries. If one dataset changed, it would still be beneficial for the other unmodified queries to remain cached so that regenerating a response happens more quickly.

Profiling Memcache performance

So far, you’ve learned how to cache expensive database queries, sessions, and full-page responses. Knowing how our cache is performing as a whole and how individual parts work is helpful.

Imagine the corresponding keys of each caching strategy you implemented had prefixes: query_, session_, and response_. Key prefixes would allow you to profile cache performance. MemCachier’s paid plans come with a powerful feature set called Introspection, allowing you to track the performance of individual prefixes, giving you deep insight into your cache. For example, you could monitor the hit rate, or hit ratio, of any prefix.

Screenshot of the MemCachier prefix introspection feature

Clean up

Once you finish this tutorial and no longer need your app, you can delete your Web Service and Database from the Render dashboard. You could also delete the corresponding Blueprint from Blueprints.

You can also delete your MemCachier cache from the Caches dashboard if you no longer need it.

Further reading & resources