WP REST API Part 2.5: Using it in WordPress 4.4

JSON response showing new plaintext field

WordPress 4.4 is set to be released in the next couple of weeks, and is bringing with it the first half of the REST API integration. This is pretty exciting news for WordPress theme and plugin developers, but if you’re wondering “what good does half of the REST API do me?”, you’re probably not alone.

Yo, Dawg.

When I read the Merge Proposal back in September, the first thing I thought was “oh cool, they’re merging the REST API.” Essentially, what we’re getting in 4.4 is a great, RESTful, replacement for the Plugin API’s admin_ajax_ hook system that provides a simpler and more robust way of making custom endpoints for plugins and apps.

There’s plenty of information about the REST API and the merge out there, so I won’t go into much more detail here. Instead, in the spirit of this series, I’d like to explore the WordPress 4.4 REST API by sketching out an app.

Give It A REST

What we’ll be creating is a very simple app that I’ll be shamelessly calling “Give it a REST” or GIAR when I’m too lazy to type it all out. If I were submitting GIAR to ProductHunt, I’d describe it as “Tinder meets Pocket for a single blog’s posts”. The app will consist of two parts:

  1. A WordPress plugin that will create the API for our app. The API will provide an endpoint to GET a list of posts, as well as an endpoint to POST upvotes and downvotes.
  2. A javascript app that will show users random blog post titles and allow them to downvote to “Give it a REST” or upvote to “Give it a READ” and add it to their reading list.

To keep things simple we’ll build this out like an MVP, or Minimum Viable Product. We’ll use very basic HTML, CSS and JavaScript/JQuery to write the app, we’ll store your reading list in localStorage, and we won’t require authentication to cast your vote.

Beginning at the Endpoint

Since this will be pretty simple, I’m going to start by scaffolding out the app first so that I can create the API endpoints based on how the app will actually use it.

The first thing the app will need is a list of articles which I’ll just mock up in JSON for now:

var posts_mockup = [
    { ID: 1, title: "First Post Title", permalink: 'http://example.com/1/', upvotes: 12, downvotes: 2 },
    { ID: 22, title: "Second Post Title", permalink: 'http://example.com/2/', upvotes: 1, downvotes: 22 },
    { ID: 33, title: "Third Post Title", permalink: 'http://example.com/3/', upvotes: 5, downvotes: 4 },
    { ID: 44, title: "Fourth Post Title", permalink: 'http://example.com/4/', upvotes: 2, downvotes: 2 },
    { ID: 55, title: "Fifth Post Title", permalink: 'http://example.com/5/', upvotes: 8, downvotes: 13 }, 
];

This is what our response should eventually look like when we request a list of posts from the API. Next, I’ll create a very simple html page that will be our app. I’ll include jQuery and app.js which will do the dirty work, as well as some basic styling, and all of the elements that our app will need to function on a basic level.

Which looks like this in a browser:

The basic page

Next, I’ve put together a very basic javascript implementation. This isn’t the best code I’ve ever written, but this is an MVP so it gets the job done:

Which has everything basically working which you can play with here:

See the Pen Give it a REST by JRGould (@JRGould) on CodePen.

Since this is a post about the REST API, I won’t go too in depth into what’s going on in the JS (feel free to ask in the comments or to fork my CodePen) but here’s a brief overview:

There’s a $( document ).ready( function() { ... that sets everything up once the document is loaded. Besides setting up click handlers for the voting buttons and the “clear” button for the reading list, the first thing it does is call getPostsFromServer() which just grabs the mocked data for now, but it also takes a callback since we’re going to be adding an AJAX call to get the real data from the server.

The callback we’re passing is initAfterAjax() which is fired once we know that we’ve got the posts from the server. The callback handles hooking data up to the DOM by showing a random post using getRandomPost() as the argument for showPost()and then setting up the reading list by clearing out the ul and looking for our existing reading list in localStorage using the getLS() helper function and using that to populate the reading list with links if it finds any.

If you read through this code, you’ll see two TODO:... comments, this is where we need to hook up the API. I’ll do this by first creating a settings object with the connection information and endpoints, this makes it easy to customize or to change in the future:

var giar_settings = {
    api_base: 'http://give-it-a-rest.dev/wp-json/give-it-a-rest/v1/',
    endpoints: {
        posts: { route: 'list-posts/', method: 'GET' },
        vote: { route: 'vote/', method: 'POST' }
    }
}

We’ll explore this more a bit later since it serves as a great outline for the API we’re going to build. Next, I’ll create an ajax helper function to utilize this object:

function doAjax( endpoint, data ) {
    return $.ajax( {
        url: giar_settings.api_base + endpoint.route,
        method: endpoint.method,
        data: data
    } );
}

This will allow us to just pass a reference to the endpoint we’d like to use along with the data we’d like to send and it will set up a basic $.ajax call based on just that. This function also returns the jQuery object so we can add callbacks when needed.

Now all that’s needed is to take care of those TODOs. First, we’ll update getPostsFromServer():

function getPostsFromServer( callback ) {
    doAjax( giar_settings.endpoints.posts, {} )
    .done( function( data ) {
        posts = data;
        if ( 'function' === typeof callback ) {
            callback.call();
        }
    } );
}

Next we’ll replace the TODO in voteOnPost() to actually send our vote:

doAjax( giar_settings.endpoints.vote, {
    vote: updown,
    id: post.ID
} );

REST assured

Now that we’ve updated our app to use data from the server, we’ll need to create the endpoints that will actually respond to it. Remember that the REST API that’s included in WordPress 4.4 doesn’t include any endpoints out of the box, so we’ll need to create our own from scratch. We’ll do this by creating a simple plugin that will be very similar to what we created in the previous installment of this series.

First we’ll hook into rest_api_init to register our routes:

add_action( 'rest_api_init', 'dt_register_api_hooks' );
function dt_register_api_hooks() {
    $namespace = 'give-it-a-rest/v1';

    register_rest_route( $namespace, '/list-posts/', array(
        'methods' => 'GET',
        'callback' => 'giar_get_posts',
    ) );

    register_rest_route( $namespace, '/vote/', array(
        'methods' => 'POST',
        'callback' => 'giar_process_vote',
    ) );
}

If you look a the giar_settings object that we created previously, this should look very similar. We start by defining a namespace give-it-a-rest/v1 and then we register two routes: a route at /list-posts/ that responds to GET requests, and another at /vote/ that responds to POST requests.

Now let’s set up those callbacks. First we’ll look at giar_get_posts:

function giar_get_posts() {
    if ( 0 || false === ( $return = get_transient( 'dt_all_posts' ) ) ) {
            $query = apply_filters( 'giar_get_posts_query', array(
                'numberposts' => -1,
                'post_type'   => 'post',
                'post_status' => 'publish',
            ) );
            $all_posts = get_posts( $query );
            $return = array();

            foreach ( $all_posts as $post ) {
                $return[] = array(
                    'ID' => $post->ID,
                    'title' => $post->post_title,
                    'permalink' => get_permalink( $post->ID ),
                    'upvotes' => intval( get_post_meta( $post->ID, 'giar_upvotes', true ) ),
                    'downvotes' => intval( get_post_meta( $post->ID, 'giar_downvotes', true) ),
                );
            }

        // cache for 10 minutes
        set_transient( 'giar_all_posts', $return, apply_filters( 'giar_posts_ttl', 60*10 ) );
    }
    $response = new WP_REST_Response( $return );
    $response->header( 'Access-Control-Allow-Origin', apply_filters( 'giar_access_control_allow_origin','*' ) );
    return $response;
}

This one’s a bit long, just because we need to transform the data, but basically all we’re doing is grabbing all posts in the database and creating a simplified version of that associative array that matches the scaffolded data that we built the data around, including upvote and downvote counts that we get using get_post_meta. We also cache the data in a transient so we’re not hammering the database too hard and provide filters for the wp_query parameters and the transient’s “time to live” or ttl.

One final note here is that instead of just returning the associative array (which would work), we’re returning a WP_REST_Response object which allows us to set the Access-Control-Allow-Origin header (which we’re also providing a filter for) to avoid CORS issues with our AJAX requests.

Now we just need to set up giar_process_vote() and we should be good to go

function giar_process_vote() {
    $vote    = $_POST['vote'];
    $post_id = $_POST['id'];

    // input validation
    if ( ! is_numeric( $post_id ) || ! in_array( strtolower( $vote ), array( 'up', 'down' ) ) ) {
        return false;
    }

    $meta_name  = 'giar_' . $vote . 'votes';
    $vote_count = intval( get_post_meta( $post_id, $meta_name, true ) );
    $update_success = update_post_meta( $post_id, $meta_name, ++$vote_count ) ? true : false;

    // clear transient posts cache
    delete_transient( 'giar_all_posts' );

    $response = new WP_REST_Response( $update_success );
    $response->header( 'Access-Control-Allow-Origin', apply_filters( 'giar_access_control_allow_origin', '*' ) );

    return $response;
}

Since this is handling post requests, we can just use $_POST to access the expected data and increment the post’s giar_upvotes or giar_downvotes metadata. We also provide a boolean response here, but we aren’t actually checking it in the app.

Now we should be ready to go! Here’s our completed plugin code:

And our completed app.js code:

If this seems similar to the previous article in this series, it is! What’s cool here is that we’re now talking about something that’s built in to WordPress 4.4 and later which means that you can download and install the above WordPress plugin on any existing site that’s running 4.4 or later, download the html file and app.js, and update the giar_settings.api_base url to match that of your site and you’re good to go! This is a silly and simple solution, but it hints at something much bigger: even with just half of the API merged into core, WordPress developers have received some really robust tools for building on top of WordPress.

About the Author

Jeff Gould

Jeff is a problem solver at heart who found his way to the web at an early age and never looked back. Before Delicious Brains, Jeff was a freelance web developer specializing in WordPress and front-end development.

  • Some of the biggest problems plaguing WP are hackers and plugin vulnerabilities. Many solutions have been created to remedy this problem – one that I became aware of recently is a hosting service that scrapes your entire front-end out to HTML and only exposes these files, while keeping the WP backend locked away. The problem with this approach is that dynamic and contextual integration breaks.

    With a REST API in place, a front-end could be created in a different framework that doesn’t rely on the WP core being in the same place – you could still use WP as an engine to feed your site, but all front-end interaction would happen through the API. This means you could even keep your WP site on a totally different server and just make API calls all day… and you could build your site with whatever front-end library you want. The downside is that WP basically becomes a database layer, but the upside is the flexibility to integrate plugins as needed.

    This approach might take more work, but the security boost might be worth it. Is this a viable strategy?

    • This idea is probably most well known as a “headless CMS” or “headless WordPress” and it’s definitely viable if your plan is to create something that’s completely bespoke. Once the second half of the API is integrated, the half with all of the core endpoints, it’ll probably start to gain some more traction.

      Where it becomes complicated is when you want to use a plugin like WooCommerce or Ninja Forms which would probably break if the html they generate is served via the API, as they’ll also require javascript libraries and create custom routes of their own that may not sync up with your headless routing implementation. Now that the API is part of core, I’d imagine that we’ll start to see solutions to this sort of problem as plugins embrace the API.

      • Kevin Stover

        Hey Eric, Jeff,

        I agree that custom UIs for WordPress will start to become very fashionable once the Rest API makes it fully into core. We’re actually in the middle of a re-write of Ninja Forms that is completely built with Backbone and Underscore and only communicates with the server via JSON. Our goal is to decouple processing and presentation; JS handles all of the stuff that the user sees via templates. None of the display (or form editing) is output by PHP.

        Currently, we use admin-ajax.php to send and receive JSON data, but when the Rest API is at least two versions old (we maintain compatibility with the two previous point versions of WP) we’ll be switching our app over to native use of it. All of our Backbone models and collections will sync with the database using routes.

        Needless to say, we’re very excited about creating the first form building app for WordPress. 🙂

  • Tanguy Sauvin

    There’s something I don’t get about wordpress. They make a REST API but there’s a plugin called JSON API which does the job and which is awesome. They make a customizer and the api is just terrible… Actually, making a REST api with Ruby-on-Rails + active admin doesn’t take a lot (as a replacement of wordpress). I guess it would be the same thing with Laravel or Django.

    For my part, I’m switching to meteor right now, I won’t get all the plugins and a really good admin area like with wordpress but for doing these kind of things, it just feels right. Maybe, I’m wrong, I’ll see.

    Sorry for not answering about the article in itself.

  • jin

    I followed this
    https://deliciousbrains.com/using-wp-rest-api-wordpress-4-4/

    and installed it

    and i changed base url

    and, how can I use it?

    after install this plugin I dont see any change,, upvote and downvote

    please answer me .

    thank you

  • Lorenzo

    Awesome post. Thank you!

  • Sunil

    Minor error on line 37
    get_transient( 'dt_all_posts' )
    I think that should be
    get_transient( 'giar_all_posts' )

  • Richard Max

    Thanks a million for this example… very useful and oh so customisable. Covers all a ‘decoupler’ needs to know… you are a gentleman and a scholar!