Over the past few years, the development of a REST API for WordPress has opened new doors for developers. Developers who had previously been limited to writing a WordPress-powered project in PHP now have more flexibility and control in how they can approach the technology stack of their website-to-be.
This means it’s possible to retain all the advantages of the brilliant WordPress control panel, which is made extremely flexible by popular WordPress plugins such as Advanced Custom Fields, and have a completely custom built frontend that only interacts with WordPress when it needs to.
In this WordPress tutorial we’ll be exploring how to implement the WordPress REST API into a simple blog app, which is currently using a local JSON file as its data source, and is built as a single-page application (SPA) using the popular JavaScript framework Vue.js. This will involve the implementation of Vuex, which we’ll use to store the data we request from a WordPress using a combination of its action and mutation methods.
Once completed, you should have created a lean, simple SPA, which has all the reactivity of Vue.js, while displaying posts retrieved from and managed by WordPress.
For more awesome APIs to explore, take a look at our guides to the best JavaScript APIs, HTML APIs and Google APIs.
01. Set up workspace and dependencies
First things first, you should download the project’s files and open them in your preferred editor.
In the console, cd into website-template and run the command below to install the project’s node dependencies (if you don’t have Node installed, do that first). We’ll be working purely in the src directory from here on out.
npm install
02. Install Vuex
Next, using the command below, we’ll install Vuex, which is a state management pattern and library for Vue.js applications. This will act as a central information store for all Vue components that depend on the data we receive from WordPress API. For developers familiar with React, Vuex is heavily inspired by Flux.
npm install vuex --save
03. Start development server
In the console, run the command below to start the development server. This will compile the Vue.js project as it currently stands and assign it to a URL so you can access it. This is usually localhost:8080.
One big advantage this brings is live reloading, so once you make changes to the app and save, the page in your browser will update itself without the need to manually reload.
npm run dev
04. Create Vuex store
In src, create a directory called store and within it a new file called index.js. This will be where our Vuex store will be defined. Though before we get to that, we need to first make sure our Vue.js app is aware of its existence. To do this, open main.js and import the store and include it as a dependency, as in the snippet below.
import Vue from 'vue' import App from './App' import router from './router' import store from './store' Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ el: '#app', router, store, template: '<App/>', components: { App } })
05. Create store scaffolding and install Axios
To help us simplify the AJAX requests our store will be making to WordPress API, we’ll install Axios, which is a Promise-based HTTP client. To do this, open a separate console window, navigate to the website-template directory and run npm install axios -- save.
Next, let’s start to scaffold the store by instantiating a new empty Vuex store object. At the moment, it isn’t doing anything, but at least Vue should be aware of it.
import axios from 'axios' import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) const store = new Vuex.Store({ state: {}, actions: {}, mutations: {} }) export default store
06. Create posts state
In Vuex, the state object holds application info, which in this case will be the WordPress post data we’ll grab using the API. Within this object, create a new property called posts and assign it a value of null.
state: { posts: null }
07. Create getPosts action
In Vuex, actions are the main way in which asynchronous requests are handled. These are typically methods defined in the store, which can then be dispatched as required by the app.
In the empty actions object, let’s define one where if our posts state is still null, axios is used to perform an AJAX request to the WordPress API and return a list of posts. Once we’ve received a positive response, we’ll then resolve the promise and commit the posts using the storePosts mutation.
getPosts: function(context) { return new Promise((resolve, reject) => { if(context.state.posts) { resolve() } else { axios.get(' webdesigner/wp-json/wp/v2/posts') .then((response) => { context.commit('storePosts', response.data) resolve() }).catch((error) => { reject(error); }); } }) }
08. Create storePosts mutation
Another concept introduced by Vuex is mutations, which are also typically methods defined in the store. Mutations are the only way to change state in a Vuex store, which allows state to be easily tracked when debugging.
In the empty mutations object, let’s define the storePosts method we referenced in the previous step and make it override the post property in the state object with any data the mutation receives as payload.
storePosts(state, response) { state.posts = response }
09. Trigger getPosts action on load
We’ve created the action and mutation methods that grab posts from WordPress API and commit them to Vuex state, but now we need to actually trigger this process somewhere. In the top level Vue.js component App.vue, add the snippet below.
created() is a lifecycle hook that triggers code as soon as the Vue component is created on load, while the use of the global $store variable allows us to access the contents of our Vuex store and dispatch the getPosts action from step 7.
created() { this.$store.dispatch('getPosts') }
10. Update attribute paths
If you’re working in Chrome or Firefox and have the Vue.js DevTools extension (if not, I recommend that you do as it’s very useful), you should now be able to see the loaded WordPress posts in Base State under the Vuex tab.
Back to the app, in /components/posts/posts.vue, the template HTML needs to point to this data, so let’s tweak a few of the attribute paths.
Switch 'post.title' to 'post.title.rendered' Switch 'post.preview' to 'post.acf.preview' Switch 'post.previewImage' to 'post.acf.header_image_small'
11. Update v-for loop
In the posts component, there’s a Vue.js directive in use called v-for. This loops through all posts and for each one prints an instance of the post component, displaying them in a list.
We need to update the path passed to this directive as it’s still using the local dummy test data. Update the v-for directive to the snippet below in order to point to our stored posts in the Vuex store.
v-for="post in this.$store.state.posts"
12. Do some housekeeping
A list of the WordPress posts should now be displaying. As we're no longer using the local post data, let's delete src/data. Then in the posts component, remove the posts: postData.data property in the components local data store and then the postData import.
13. Fix post author
You may notice that for each post the author is showing up as a number. This is because the WordPress API returns an author ID, rather than a name. We need to use this id to query WordPress for the full author information. Let's start by creating a place to store this in our Vuex store, alongside our post info, in store/index.js.
state: { authors: null, posts: null }
14. Create getAuthors action
As with posts, we'll create an action in /store/index.js to trigger an AJAX request to query WordPress API. Once a successful response is received, the promise will then resolve and trigger the storeAuthors mutation, which we'll create next.
getAuthors: function(context) { axios.get(' webdesigner/wp-json/wp/v2/users') .then(function(response) { context.commit('storeAuthors', response.data) }) }
15. Create storeAuthors mutation
Within the mutations object of the Vuex store, create the storeAuthors mutation using the snippet below. Like with storePosts from step 8, this takes the payload its passed and sets it as the value of the authors property in our store's state.
storeAuthors(state, response) { state.authors = response }
16. Trigger getAuthors on load
We need to get the author info from WordPress as soon as the app begins to load. Let's amend the top level component App.vue again and dispatch the getAuthors action in the same created() lifecycle hook as the getPosts action.
created() { this.$store.dispatch('getAuthors') this.$store.dispatch('getPosts') }
17. Create getUserName method
Now we're querying WordPress for author information on load, all we need to do is define a method in our posts component which lets us pass an author ID and get a name in return. Copy the snippet below into the posts component's methods object, below the existing getSinglePost method.
getUserName: function(userId) { var username = 'unknown'; this.$store.state. authors.filter(function(user) { if(user.id === userId) { username = user.name } }); return username; }
18. Call getUserName method
Now we just need to call getUsername. Still in the posts component, in the template, replace the author attribute’s reference to post.author so it reflects the snippet below. The author’s name should now be correctly displaying for each post.
:author="getUserName(post.author)"
19. Blog loading
As we’re loading the post data asynchronously, there is a moment before the request completes where the application is empty. To counter this, we’ll implement a loading state that's active until the blog is fully populated. In the posts component, paste the snippet below just after the opening <script> tag to import the icons we’ll be using.
import icon from '@/components/icons/icons' import loading from './../../ assets/img/loading.svg'
20. Add icon to components list
Next, still within posts, add a reference to icon in the components objects. This makes the posts component aware of our recently imported icon component.
components: { icon, post }
21. Create loading elements
We now just need to add the loading elements to the posts template so it shows up on the page. Firstly, wrap the second div in the snippet around the two divs with the v-if directives to make sure no posts show up until loading is complete.
Then add the first div from the snippet above it. This contains the loading icon and a v-if directive, which means it will only be visible until the point where the app is fully loaded. Once done, loading should now be implemented.
<div v- <icon class="c-icon-loading" use="loading"></icon> </div> <div v- [...] </div>
22. Update single post attribute paths
The only thing left to do is to make sure single posts are correctly set up so they are using the WordPress post data in the Vuex store. The first step is to update the attribute paths in the posts component template within the v-if="this.type === 'single'" div, which handles the display of single posts.
Switch 'singlePost.title' to 'singlePost.title.rendered' Switch 'singlePost.author' to 'getUserName(singlePost.author)' Switch 'singlePost.fullImage' to 'singlePost.acf.header_image' Switch 'singlePost.content' to 'singlePost. content.rendered'
23. Refactor getSinglePost method
We also need to refactor the posts components getSinglePost method. It needs to return a promise that dispatches the getPosts action. In the follow up then function, we'll search the Vuex store's posts for an entry with a slug matching the one passed in the URL. If found, we'll copy the data to our component's local state and resolve the promise. If it isn't found, the promise will be rejected.
getSinglePost: function() { return new Promise ((resolve, reject) => { this.$store.dispatch('getPosts') .then(() => { var foundPost = false; this.$store.state.posts. filter((post) => { if(post.slug === this.$route.params.slug) { this.singlePost = post; foundPost = true; } }); foundPost ? resolve() : reject(); }) }) }
24. Refactor posts created() hook
Next, we need to refactor the created() lifecycle hook in the posts component. If we're needing to display a single post, the hook should call the getSinglePost method from the previous step, and if its promise is rejected, send the user to the 404 'page not found' page. This is to account for scenarios where users enter a non-existent post slug in the URL.
created() { if(this.type === 'single') { this.getSinglePost().then(null, () => { this.$router.push({name: 'pageNotFound'}) }); } }
25. Add v-if directive
The final step is to add the snippet below to the post component within the v-if="this.type === 'single'" div in the template. This directive means the post will only display when the local post data made available by the getSinglePost method is populated. This is to stop Vue from prematurely rendering the component and thus causing errors.
v-if="singlePost"
26. Build the app
Now with everything working, in the console, cancel the npm run dev command or open a new console and run the below command to generate a production ready version to upload to your own server. This will appear in the dist directory.
npm run build
This article appeared in issue 268 of Web Designer, the creative web design magazine – offering expert tutorials, cutting-edge trends and free resources. Subscribe to Web Designer now.
Read more: