The WordPress REST API gives you the ability to combine content from multiple sites. This is extremely helpful as using more than one CMS to serve content to one site can solve a lot of problems. For example, you could solve scaling problems by dedicating one WordPress install to each post type. Or, if your organization has many sites, you can use the REST API to share content between totally separate sites.
Recently I faced the challenge of building a site to bring together all of the different brands in my company. We intended to keep each brand on their own WordPress install but wanted to combine all of the blogs on one page of the company site. For this, we created a simple HTML site and pulled in content from all of the different WordPress’ sites using JavaScript.
In this article, I’m going to provide a detailed walkthrough on how to use the REST API to combine posts from multiple WordPress sites. It’s also a good example of how to use JavaScript promises and leverage the browser’s local storage API. In my next post, I will discuss a few similar ways to combine content, but I will be using PHP instead of JavaScript as those uses are inside of WordPress.
Why Promises Are Important
In the code below I will make three AJAX calls to get posts. Then before outputting them, I will merge and sort the responses into one array. The tricky part about doing so is the “A” in AJAX — asynchronous.
Each call happens simultaneously and takes a different amount of time to complete, so there is no way of knowing when all three calls are done.
This problem does not exist in PHP, however, because it is serial, meaning that it does one thing at a time. Consider this:
$posts1 = wp_remote_get( 'https://CalderaWP.com/wp/v2/posts' ); $posts2 = wp_remote_get( 'https://IngotHQ.com/wp/v2/posts' ); $posts = array_merge( $posts1, $posts2 );
The PHP will wait until the HTTP request on the first line is complete before making the request on the second line. Then, after that request is done, it merges the two resulting arrays, which makes things simple, but not very performant.
With jQuery AJAX, we can make many HTTP requests simultaneously. But it also makes the code more complicated. Here is something similar using jQuery AJAX, that will not work:
var posts1, posts2, posts; $.get( 'https://calderawp.com/wp-json/wp/v2/posts' ).done( function(r ){ posts1 = r; }); $.get( 'https://IngotHQ.com/wp-json/wp/v2/posts' ).done( function(r ){ posts2 = r; }); //errors are about to happen posts = posts1.concat( posts2 ); console.log( posts );
The problem is that JavaScript will start the two HTTP requests and then immediately try to merge the variables posts 1 and posts 2. There is very little change that the HTTP requests will be complete by then.
Roy Sivan earlier this year wrote an excellent introduction to promises in JavaScript for Torque, which is definitely worth checking out. jQuery AJAX already uses promises, so we just need to move our array combining to a callback triggered by resolving promises of both requests.
There are a lot of libraries out there for handling JavaScript promises. jQuery’s $.when() function is all we really need to rewrite this code. Here is a working way to merge the responses of both calls:
var posts; $.when( $.get( 'https://calderawp.com/wp-json/wp/v2/posts' ).done( function(r ){ return r; }), $.get( 'https://IngotHQ.com/wp-json/wp/v2/posts' ).done( function(r ){ posts2 = r; }) ).then( function( d1, d2 ) { posts = d1[0].concat( d2[0] ); console.log( posts ); });
This is a little messy, and I will show how to make this cleaner, and use local storage shortly. But, it illustrates the idea. The basic pattern is:
$.when( //one or more functions that return a promise ).then( function(){ //stuff to do after all promises are resolved });
You can read this as “when I’m done with this stuff, then do this stuff.” It ensures that our merging only happens when we have data to merge.
An API For Your API
Now that you understand promises, you can put a few of them together. Just remember to be careful because you can end up making a huge pyramid of promises and it gets messy and anti-DRY fast.
Instead, let’s make a mini-API for querying the REST API. That way we can have one function for all GET requests. Not only does this help keep the code DRY, but it makes it simple to implement local storage.
The API function needs to craft a cache key and check if there is any data in the local storage with that key. If not, we make the GET request and cache the data for next time in local storage. If the data exists in local storage, that is returned and the request is skipped.
Here is the code:
function api( url) { var key = 'clCache' + url.replace(/^https?\:\/\//i, ""); var local; //this line uses underscores.js! if ( ! _.isString( local ) || "null" == local ) { return $.get( url ).then( function ( r ) { r = self.addPrefixes( r, prefix ); localStorage.setItem( key, JSON.stringify( r ) ); return r; } ); }else { return JSON.parse( local ); } }; Raw
Notice that I actually put return before the $.get() function and in its callback. This is necessary to ensure that you are returning a promise object. If not, when used in a $.when().this() call, the promise will be resolved immediately, not after the request.
Now we can make two functions that wrap this API and then call them together using $.when() –
jQuery( document ).ready( function ( $ ) { function getCWP() { return api( 'https://calderawp.com/wp-json/wp/v2/posts' ); } function getIngot() { return self.api( 'https://ingothq.com/wp-json/wp/v2/posts' ); } function api( url) { var key = 'clCachez' + url.replace(/^https?\:\/\//i, ""); var local; //this line uses underscores.js! if ( ! _.isString( local ) || "null" == local ) { return $.get( url ).then( function ( r ) { localStorage.setItem( key, JSON.stringify( r ) ); return r; } ); }else { return JSON.parse( local ); } }; var posts; $.when( getCWP(), getIngot() ).then( function( d1, d2 ) { posts = d1.concat( d2 ); console.log( posts ); }); } );
Now I have an array with posts from both sites. Since the data is coming from local storage after the first request, it can load really fast. In fact, on the site I built this for, I actually had it fetch the posts without showing them on the main page. That way if you go to the site’s main page, and then navigate to the blog, the posts are already stored in the browser.
Showing The Posts
You may be wondering why in my example code I used underscores.js for checking if the data existed in local storage. Adding a whole dependency just for one line of code is a little crazy. But I pretty much always use underscores.js or lodash because of the helpful array sorting functions and the simple, lightweight templating.
The next thing we need to do is merge and sort the objects returned by the API and put them in one array. That can get pretty complicated, especially since in my actual use I have three APIs I’m working with and will add more in the future. The underscores.js library helps a lot with this.
I created two utility functions to handle the merging and sorting:
function merge(obj1, obj2) { var merged; a1 = _.toArray(obj1); a2 = _.toArray(obj2); merged = a1.concat(a2); return merged; }; function sort( list, sort ) { var sorted = _.sortBy( list, sort ); return sorted.reverse(); };
The last step is to loop through that array to create HTML markup. I’m a big fan of Handlebars.js for JavaScript templating. It adds complex logic and other helpers to the very minimal Mustache templating language.
In this case, I don’t need any of those advanced features of Handlebars.js because underscores.js has a good, minimal templating system. But, with a few lines of code, it can be switched to use Mustache-style syntax, which is far simpler.
Here is the code to put in the $.then() callback that merges the two objects returned by the API and outputs them using a template:
var posts; $.when( getCWP(), getIngot() ).then( function( d1, d2 ) { var all = merge( d1, d2 ); all = sort( all, 'date' ); _.templateSettings = { interpolate: /\{\{(.+?)\}\}/g }; var html = ''; var templateHTML = document.getElementById( 'tmpl--multiblog' ).innerHTML; var template = _.template( templateHTML ); var item, templatedItem; $.each( all, function ( i, post ) { templatedItem = template( post ); html += templatedItem; }); document.getElementById( 'posts' ).innerHTML = html; });
This requires that the template and the container for placing the template be in the DOM. Here is the HTML for that:
<div id="posts"></div> <script type="text/html" id="tmpl--multiblog"> <article class="entry multi-blog-{{prefix}}"> <span class="image"> <img src="placeholder.png" alt="" class="img-{{prefix}}" data-media-id="{{featured_media}}" /> </span> <a href="{{link}}" title="Read Full Post"> <h2>{{title.rendered}}</h2> <div class="content"> <p> {{excerpt.rendered}} </p> </div> </a> </article> </script>
Make It Your Own
If you want to see how I brought all of this together, you can check out the site, and read all of the code that created it. We host that site on Github pages, but there is no reason it could not be sitting on the same server as one of the WordPress sites we are using with it, or on AWS, or anywhere else. I really think this hybrid static HTML site/WordPress site opens up a ton of possibilities.
Now that you’ve learned how to combine multiple WordPress REST API endpoints into one blog via jQuery you can take this a lot further. You can play with delivery methods for the site or caching to improve performance. You can also add a filtering system to sort or limit the results based on user input. Don’t forget that you could also be showing custom post types, such as products.
3 Comments