When I start building a new SPA project, I focus extensively on the workflow of the app. I focus on how it should look and how the user would interact with it. Once all of this is done, I move my focus to where the data is coming from. But until then, I use mock data instead of real data.
One way you can use mock data is simply by adding them directly to your Vue components, or to your Vuex store. But doing this makes it harder to switch to real data when the API is ready to be used.
Not only that, but we also need a way to make switching between data sources very easy — it should be as easy as changing a single line of code. This would be very helpful when we don’t have an internet connection, or when we’re implementing a feature in the frontend that isn’t supported in the backend yet.
Also, using mock data can make the development of your app much faster. For example you don’t have to wait for a slow server to respond (assuming it’s not running locally).
Now we know why we need to have mock data in our Vue app, let’s dive in and learn how to do it.
Note: this tutorial assumes that you understand Vuex.
Setting up the project
Let’s quickly scaffold out a new project with Vue CLI. Note that we’re using Vue CLI 3 here. So make sure it’s installed before running the following command.
vue create vue-mock-data
This command should display the available project types. For our example, choose typical-spa.
In our example, we’re going to have a list of post titles stored in Vuex. We will begin by adding a bunch of mock posts directly to the state object. And later, we’ll move them to a place dedicated to mock data.
So add these post titles into src/store.js
:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
posts: [
{ title: 'Post Title 1' },
{ title: 'Post Title 2' },
{ title: 'Post Title 3' },
{ title: 'Post Title 4' },
{ title: 'Post Title 5' }
]
},
mutations: {},
actions: {}
})
Displaying the posts
In this tutorial, we’re not concerned about how the app should look and feel. We just need to learn how to implement a way to switch between mock data and real data so easily.
So let’s just display those post titles on some page to see where the data is coming from.
Now let’s modify the homepage view component to display our post titles from our Vuex store.
So, let’s update src/views/Home.vue
to become like this:
<template>
<div class="home">
<ul class="posts">
<li v-for="post in posts" :key="post.title" class="post-item">
<h1>{{ post.title }}</h1>
</li>
</ul>
</div>
</template>
<script>
export default {
computed: {
posts() {
return this.$store.state.posts
}
}
}
</script>
<style scoped>
.posts {
list-style: none;
text-align: left;
}
.post-item + .post-item {
border-top: 1px solid rgba(0, 0, 0, 0.1);
}
</style>
If you check the browser, you should see all posts displayed like this:
Using the dynamic api client
The basic idea of this is to have two data sources that implement the same api methods. And to switch between them, we should have some kind of a config value that determines which source to get the data from.
Since both sources (api clients) are implementing the same api methods, we can write the fetching code only once without any conditions or so — this is called coding to an interface, by the way.
Let’s write that fetching code inside store.js
.
import Vue from 'vue'
import Vuex from 'vuex'
import client from 'api-client'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
posts: []
},
mutations: {
setPosts(state, posts) {
state.posts = posts
}
},
actions: {
fetchPosts({ commit }) {
return client.fetchPosts().then((posts) => commit('setPosts', posts))
}
}
})
Two things to notice here. First, we defined an action and a mutation for fetching and setting the posts — if you’ve done any work with Vuex before, this shouldn’t feel foreign to you. Second, we’re importing an api client object that we don’t have yet. This object is what we’re going to use to interact with our api. It will either fetch data from a real server or from our mock data store.
So all of this leaves us with two things to do:
- Implement those two api clients — one for the real data and the other for the mock data.
- Implement the config option that determines which api client to use.
Implementing both api clients
Our current step is to implement both api clients that we’re going to dynamically resolve as the api-client
module. We’ll have everything related to our api in a new directory: src/api
. In this directory, create another two subdirectories, one is called mock
and the other is server
. Then in each one, add a new index.js
file.
Now let’s implement the mock api client first. But before that, let’s create a new directory inside src/api/mock
to have all of our mock data, stored in JSON files. Let’s name that directory data
.
Let’s create our first mock data file inside src/api/mock/data/
, name it posts.json
, and then put this into it:
;[
{ title: 'Post Title 1' },
{ title: 'Post Title 2' },
{ title: 'Post Title 3' },
{ title: 'Post Title 4' },
{ title: 'Post Title 5' }
]
To make sure we’re on the same page, here’s how your api
directory should look like:
api
├── mock
│ ├── data
│ │ └── posts.json
│ └── index.js
└── server
└── index.js
Now it’s time to implement our mock api client in mock/index.js
:
import posts from './data/posts'
const fetch = (mockData, time = 0) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(mockData)
}, time)
})
}
export default {
fetchPosts() {
return fetch(posts, 1000) // wait 1s before returning posts
}
}
The basic thing this file is doing is exporting an object with a method to fetch posts. But note that this method has to be named fetchPosts
, because that’s what we’ve used in store.js
, remember? Also, I’ve created a helper function to simulate data fetching. This function also gives us the ability to specify how long it should take to fetch the data — this would be useful for showing loading indicators.
Now the mock api client is ready, let’s implement the server api client. Put the following into src/api/server/index.js
:
import axios from 'axios'
export default {
fetchPosts() {
return axios
.get('https://jsonplaceholder.typicode.com/posts')
.then((response) => response.data)
}
}
Like the mock api client, we’re exporting an object with fetchPosts
method. In this method, we fetch posts from some fake api.
Note that we’re using axios
to fetch the data from the api. So don’t forget to install it before moving to the next step.
npm install axios --save
Implementing api-client resolver
Webpack provides us with a way to determine how to resolve a specific module when we’re importing it with the import
syntax. This feature is called Resolve.
In Vue CLI 3 we can chain Webpack configs with the chainWebpack
function. And in that function, we can add a resolve rule that resolves api-client
to either src/api/mock/index.js
or src/api/server/index.js
.
You can think of resolving a module simply as replacing the path of the imported module (in this case it’s api-client
) with whatever path we want.
We define our vue configs in vue.config.js
file. So create it in your project root directory and put this into it:
const path = require('path')
module.exports = {
chainWebpack: (config) => {
const apiClient = process.env.VUE_APP_API_CLIENT // mock or server
config.resolve.alias.set(
'api-client',
path.resolve(__dirname, `src/api/${apiClient}`)
)
}
}
As you can see in the code above, we’re resolving api-client
to src/api/${apiClient}
, where apiClient
is what we get from our environment variables.
You can learn more about environment variables from Vue CLI 3 docs.
In our example, we’re going to define two env files, for production and development modes. In the development env, we’re going to set the api client to mock
, but for production, we’re going to set it to server
.
So create .env.development
and .env.production
in your project root. Then, put this into .env.development
:
VUE_APP_API_CLIENT = 'mock'
And this into .env.production
:
VUE_APP_API_CLIENT = 'server'
Updating the Homepage
Our last step in this tutorial is to update our view page to fetch data using our fetchPosts
Vuex action. We’ll also improve it by displaying some kind of a loading indicator while it’s fetching.
Ultimately, your Home.vue
should look like this:
<template>
<div class="home">
<span v-if="loading">Loading…</span>
<ul v-else class="posts">
<li v-for="post in posts" :key="post.title" class="post-item">
<h1>{{ post.title }}</h1>
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
loading: false
}
},
computed: {
posts() {
return this.$store.state.posts
}
},
created() {
this.loading = true
this.$store.dispatch('fetchPosts').then((posts) => {
this.loading = false
})
}
}
</script>
<style scoped>
.posts {
list-style: none;
text-align: left;
}
.post-item + .post-item {
border-top: 1px solid rgba(0, 0, 0, 0.1);
}
</style>
Now you can finally test your work by changing VUE_APP_API_CLIENT
in the targeted environment to either mock
or server
. But note that each time you change it you have to restart/rebuild your app.
That’s it! Now you can easily switch your app data source by changing a single line in your targeted .env
file.
Note: You can get the source code of this tutorial from GitHub.