REST Endpoints in WordPress, Creating an Endpoint for Menus
With the WordPress REST API, we can rely on parts of our core site CMS to dictate consistent data to other single page applications we build.
We ❤️ WordPress and the REST API.
We love WordPress so much so that we rely on parts of our core site CMS to dictate data sent to other single page applications we build.
Recently we started digging a little deeper into using React for our site performance analyser.
When creating the application, we installed our pattern library as a git submodule, and imported our core stylesheet that we use in all of our self-owned products.
We then went about setting up the page, with components that we often find ourselves using such as forms, inputs, hero banners, etc.
One thing we noticed when creating the header navigation for this app was the duplication of the menu items between our core site and the app itself.
These menu items were hardcoded in, as a result, if we were to up date our main site with a new menu, the single page application wouldn't update.
This is where a nifty JavaScript fetch function comes into play.
Fetch and WP REST JSON
In our fetch function, we are grabbing JSON from a custom endpoint that we defined in our WordPress functions.php file (more about that in a bit).
Endpoint can be viewed at - https://noface.local/wp-json/menus/v2/header
fetch("https://noface.local/wp-json/menus/v2/header")
.then(function(response) {
return response.json();
})
.then(function(myJson) {
for (var i in myJson) {
currentMenu.push({
href: myJson[i].url,
id: "list-item-" + i,
text: myJson[i].title
});
}
self.setState({ menuItems: currentMenu });
});
Now at this point, you can alternatively look at WordPress plugins that offer out of the box solutions such as this one - https://wordpress.org/plugins/wp-api-menus/
Heck, we used it for a day ourselves.
Once we had a proof of concept was working, we uninstalled the plugin in favour of a custom WordPress REST API endpoint.
Why did we go through the effort of integrating our own endpoint?
- Flexibility
- Speed
- So we can write a blog post about it
On a more serious note, it's impressive that WordPress allows for custom endpoints.
We relied heavily on the concept of custom routes and queries to build Expelled Skateboarding.
Creating GeoJSON endpoints that we could plug straight into Google Maps, without any converting, reordering or formatting of any sort!
Our custom endpoint function
Now before you read this mammoth sized piece of code, we have added inline comments to help explain what each part of the function is doing.
We are not PHP gurus, we love WordPress from a developers standpoint, but we work heavily with Timber and Twig themes.
So please don't be too harsh on our choice of syntax 😇
<?php
/* Register function to run at rest_api_init hook */
add_action( 'rest_api_init', function () {
/* Setup siteurl/wp-json/menus/v2/header */
register_rest_route( 'menus/v2', '/header', array(
'methods' => 'GET',
'callback' => 'header_menu',
'args' => array(
'id' => array(
'validate_callback' => function($param, $request, $key) {
return is_numeric( $param );
}
),
)
) );
} );
function header_menu( $data ) {
/* Verify that menu locations are available in your WordPress site */
if (($locations = get_nav_menu_locations()) && isset($locations[ 'header-menu' ])) {
/* Retrieve the menu in location header-menu */
$menu = wp_get_nav_menu_object($locations['header-menu']);
/* Create an empty array to store our JSON */
$menuItems = array();
/* If the menu isn't empty, start process of building an array, otherwise return a 404 error */
if (!empty($menu)) {
/* Assign array of navigation items to $menu_items variable */
$menu_items = wp_get_nav_menu_items($menu->term_id);
/* if $menu_items isn't empty */
if ($menu_items) {
/* for each menu item, verify the menu item has no parent and then push the menu item to the $menuItems array */
foreach ($menu_items as $key => $menu_item) {
if ($menu_item->menu_item_parent == 0) {
array_push(
$menuItems, array(
'title' => $menu_item->title,
'url' => $menu_item->url
)
);
}
}
}
}
} else {
return new WP_Error(
'no_menus',
'Could not find any menus',
array(
'status' => 404
)
);
}
/* Return array of list items with title and url properties */
return $menuItems;
}
?>
If you copy and pasted the code above, be sure to swap out header-menu
for the name of your menu.
Once included in your themes functions.php
file, you'll now have an available WP REST API route at yoursite.com/wp-json/menus/v2/header.
If you've given the WP API Menus plugin a go, you'll see that there's a massive difference in the data found between our custom route and the plugin.
We're only exporting the data we need (Title text and URL).
This reduces the amount of data WordPress needs to export and reduces the amount of data we process with our JavaScript in our react function.
Conclusion
We've set up a custom route for our menu data.
We've also fetched and processed the data into a navigation that remains consistent across our software.
Pretty good for an hour's work if you ask me!
If you want to get serious about performance, you could also look into caching your routes via plugins such as - https://wordpress.org/plugins/wp-rest-api-cache/