Using Laravel and WordPress to build a fast and flexible API – Part 1

Laravel makes it powerful and WordPress makes it simple.

Categories: Development, WordPress

Making your own API is probably faster and easier than you thought. By using Laravel with WordPress I will guide you through how I was able to make an API in a few hours that has served 40+ websites and dished out more than 2 million events a month.

I will go over how to query the WordPress database using native WordPress queries from within Laravel. In part 2 I will show you how to improve performance with caching and some workflow improvements.

First a little background

I am responsible for a large automotive website, And about 40+ external dealership sites. Each site operates off of a number of different services like Leads, Images, Analytics, Data Collection, Vehicle Information to name a few. Each site is service driven and therefor more or less static. Any Updates are done in HTML where needed otherwise I use a version of the API that I will be going over in order to make frequent edits to the site.

In total there are about 11,000 vehicles listed on Go Auto and anywhere from a couple of hundred to a few thousand vehicles per dealership. Each site consists of information that needs to be managed and updated. Store information, Slides, SEO details, Campaign Landing Pages, and what I call nodes – flexible blocks of information that we might need.
The company site is its own instance and every single dealership site runs off of one Laravel instance like a large multisite. The infrastructure for these sites includes AWS servers with Docker containers and no Database.

All of these sites are also pushing close to 2 million page views a month, Through caching the API served over 170,000 requests.

The API has allowed us to make updates that otherwise would require development time from my team. These updates can now be done by content creators or the design team.

Let’s get started!

For this to happen I have two URLs. One is the main API endpoint and the other one being the private WordPress admin.

In my case, I use a subdomain like admin.example.com and api.example.com, but you can use separate domains like laravel-api.com and api-admin.com.

Why Laravel?

So why not just use the WordPress JSON API? Using the WordPress API is a valid solution and likely a good one if you need to update WordPress content.

The WordPress API isn’t without its own issues. In my experience and due to the nature of WordPress every API request will trigger a reaction to events and hooks within WordPress. Most of them unrelated to your actual API. It also requires you to register (endpoints)[https://developer.wordpress.org/rest-api/extending-the-rest-api/adding-custom-endpoints/] but it’s slow and who am I kidding? No one really likes to develop in WordPress.

Using Laravel gives us multiple advantages such as an easy interface for Caching, Routes, Authentication if we needed it, Debugging, Error reporting/logging and an easy way to handle Requests.

The Task At Hand

Build an API that can be updated easily
It has to be fast
It needs to handle a lot of requests
Needs to allow for the addition of new data

Start by Installing Laravel

Next, start off by grabbing a fresh copy of Laravel 5.4 by running composer create-project laravel/laravel blog "5.4.*" --prefer-dist.

If this command throws an error have a look at how you quickly set up a new Laravel project here.

Remember, This is a regular Laravel application so you can include other Composer packages if needed but that will be outside the scope of this article.

Now that we have that out of the way, Configure your API URL and set the document root to /public. Load your new API URL in the browser and you should see the Laravel Welcome Page.

Installing WordPress

WordPress will be installed within the root of the Laravel project folder.

If you have WP CLI installed then run wp core download --path=admin from within the Laravel project. If you don’t have WP CLI installed then download WordPress manually from wordpress.org and extract the contents into your project folder.

Do not place WordPress in the public folder!

Install and setup WordPress as normal.

Download and install this simple theme. This theme won’t have any templates or assets. It is a shell of a theme with only the minimum required to allow WordPress to register it.

I will touch on two other important features of this theme a little later on.

At this point, you should have a fully working and 100% stock WordPress install.

Make sure to configure your site to Visiting your site URL should be a blank page.

Set up your admin URL and make sure that your document root is /admin. You should now be able to visit the admin site and at this time it will be a regular WordPress install.

In order to let Laravel access WordPress functionality we need to make a few simple adjustments inside of WordPress.

Let’s start by opening the wp-config.php file.

WordPress uses a web-based cron, not like the standard Unix cron jobs to deal with scheduled content, and clearing transients data. Feel free to leave it turned on if you think that you might want to take advantage of scheduled content but you will see a performance if you use New Relic to keep an eye on your performance.

// Disable WP Cron.
define('DISABLE_WP_CRON', 'true');

As a note. It is possible to set up a server-side cron that would request http://example.com/wp-cron.php?doing_wp_cron and it can trigger all the standard events for you.

Configuring Laravel

Open index.php inside of Laravel’s /public folder, at the very top add the following:

/*
|--------------------------------------------------------------------------
| WordPress
|--------------------------------------------------------------------------
|
| Integrate WordPress with Laravel core
|
*/


define('WP_USE_THEMES', false);
require __DIR__.'/../admin/wp-blog-header.php';

Read more on WP_USE_THEMES

Let’s get the API up and running now!

If you would like to keep your api.example.com/api/v1 then ignore this step but personally, I want my URL to be api.example.com/v1. So we need to make a small change to Laravel’s Route Service Provider.

Before we do this, there are two considerations to make:

Do we need API middleware?
And what do we want our URL to look like?

I don’t need the middleware and do not want to have /api so all of my routes will be placed in the web.php route file.

If you find that you want to keep the middleware then open app/Providers/RouteServiceProvider.php and look for Route::prefix('api') somewhere around line 66 and change that to Route::prefix('v1').

Otherwise, rename

// protected function mapApiRoutes()
->group(base_path('routes/api.php'));

to

// protected function mapApiRoutes()
->group(base_path('routes/api-v1.php'));

Let’s add two simple routes to deal with store information and staff.

Add the following routes to your API routes file.

Route::get('/stores', function () {
    return ['stores'];
});

Route::get('/staff', function () {
    return ['staff'];
});

Checking api.example.com/v1/stores you should see a simple output of

[
    "stores"
]

It works! But wait.

Our test is good, So let’s move this to a controller, create a new one called ApiController and update your routes to reflect this:

Route::get( '/', 'ApiController@index' );
Route::get( '/store', 'ApiController@stores' );
Route::get( 'store/{slug}', 'ApiController@store' );
Route::get( '/staff', 'ApiController@staff' );
Route::get( '/staff/{slug}', 'ApiController@staff' );

You might see fit to create a ‘StoreController’ as well as a ’StaffController’ which would be a good idea for your own API.

Create the index view:

public function index() {
    return [
        'store' => Request::url() . '/store',
        'staff' => Request::url() . '/staff',
    ];
}

By accessing /v1 you should see:

{
    "store": "http://api.example.com/v1/store",
    "staff": "http://api.example.com/v1/staff"
}

We really don’t have to list the endpoints but personally, I think its good design creates an accessible API.

Now create the store ”view”, staff will be functionally the same.

Log into your WordPress admin and Install and activate Custom Post Type UI.

Add a new post type called store. Optionally you can remove support for the editor and featured image.

Now install ACF. ACF is a premium plugin at $25 per site or $100 for a developer license. As far as I am concerned it’s priceless and probably one of the most useful plugins ever made for WordPress. It puts structured and repeatable data at your fingertips.

Under Custom Fields, Create a new field group called Stores and add some fields to your store:

  • name – text
  • address – textarea
  • phone – text
  • email – email
  • image – image / image array / upload to post

Under location rules set the Post Type | Is Equal to | Store.

Adding a couple of stores. The title will be your API slug so add your title and change the slug if you need to, This will be independent of the store name and used for singular requests in your API.

We are about to pull it all together and really get into the meat and potatoes of the API.

So far we have installed Laravel and WordPress inside of your Laravel project. We changed a couple of lines of code inside of the wp-config.php and have used two plugins to set up and add custom post types with inputs needed to populate data in the API. We also bootstrap WordPress in Laravel’s index.php file giving us access to everything WordPress has to offer.

I didn’t mention it previously but you could add custom functions to your WordPress functions.php like specifying custom media sizes.

Here is the complete store endpoint method if you want to jump right in, I will go into finer detail below.

public function store( $slug = null ) {

    if ( $slug === null ):
        $queryString = array(
            'post_type'      => 'store',
            'posts_per_page' => - 1,
            'orderby'        => 'date',
        );
    else:
        $queryString = array(
            'post_type'      => 'store',
            'name'           => $slug,
            'posts_per_page' => 1,
            'orderby'        => 'date',
        );
    endif;

    $query = new \WP_Query( $queryString );
    $posts = $query->get_posts();

    foreach ( $posts as $post ):
        $single_acf = get_fields( $post->ID );

        if ( empty( $single_acf ) ) {
            $single_acf = null;
        }


        $object['results'][] = [
            'data' => $single_acf
        ];
    endforeach;


    return $object;
}

Accessing /store will set up the WordPress query for multiple items or posts requesting /store/store-name would result in a single lookup by the slug name.

Shocker! Did you notice that this is standard WordPress code?

From what I have found, anything you can do with WordPress queries can be done within these API endpoints.

Read more on WP_QWuery

if ( $slug === null ):
    $queryString = array(
        'post_type'      => 'store',
        'posts_per_page' => - 1,
        'orderby'        => 'date',
    );
else:
    $queryString = array(
        'post_type'      => 'store',
        'name'           => $slug,
        'posts_per_page' => 1,
        'orderby'        => 'date',
    );
endif;

Next, we will execute the query and assign it to $posts

$query = new \WP_Query( $queryString );
$posts = $query->get_posts();

Looping over the post object we can now get each stores post metadata using ACF.

foreach ( $posts as $post ):
    $single_acf = get_fields( $post->ID );

    if ( empty( $single_acf ) ) {
        $single_acf = null;
    }

    $object['results'][] = [
        'data' => $single_acf
    ];
endforeach;

return $object;

Finally, the object is returned.

You should be able to take what you have learned and build out the staff endpoint.

In Closing

As you can see if you have a basic to good understanding of WordPress and its relations you can accomplish a lot with a few lines of code and a handful of basic plugins.

This kind of API is very simple, it’s also fast and flexible. The ability to leverage WordPress as an admin while taking full advantage of great plugins like ACF and Custom Post Type UI gives you an incredibly powerful API that has a familiar Admin interface.

You could get a little more adventurous and consider wildcard routes possibly allowing you to never need to make updates to the code base.

While not necessary I would recommend using WP Offload Media as this will place your WordPress media content into Amazon S3 giving you a bit more separation from the API in your Media URLs.

In the next article, I will go over how you can set up caching, Authentication headers, The ability to clear cache for a result, as well as adding some useful information to your API response.

Enjoy Part 2!


Adam Patterson

Adam Patterson

User Interface Designer & Developer with a background in UX. I have spent 5 years as a professionally certified bicycle mechanic and ride year-round.

I am a husband and father of two, I enjoy photography, music, movies, coffee, and good food.

You can find me on Twitter, Instagram, and YouTube!