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:
- 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 toPOST
upvotes and downvotes. - 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:
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 TODO
s. 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.