Build a Vuex Module

Part 9 of 48 in API Driven Development With Laravel and VueJS
Dan Pastori avatar
Dan Pastori October 16th, 2017
⚡️ Updated content is available We learned a lot since we originally wrote this article. We now have this updated for Laravel 10, Vue 3/NuxtJS 3, and Capacitor 3.

In the last tutorial Build Out API Requests in Javascript , we went through building methods to call our Laravel API routes with Javascript. We built the /resources/assets/js/api/cafe.js file which contains the front end requests to our backend API that we built here: Add Laravel API End Points – Server Side Up. Now we are at the point where we need to store the data we get from the API so we can use it in our Single Page Application. That’s where Vuex Modules come in!

According to the Vuex documentation What is Vuex? · Vuex “Vuex is a state management pattern + library for Vue.js applications. It serves as a centralized store for all the components in an application, with rules ensuring that the state can only be mutated in a predictable fashion. “ What this translates to is it’s a single point of data that can be reused across multiple components and multiple pages. Why would you want this? As you build larger, more complex single page applications, you end up using data in multiple places. For example, you have a user that’s logged into your app. Instead of passing that user in as a parameter to each component that uses that user’s first name and last name, you can store it in a Vuex module and access that data whenever is needed. We will be using that example and many more vuex modules in Roast.

Vuex is also extremely helpful in tracking the state of the data in your application. If you are using the dev tools, you can see the data each module holds and how to access it.

Vuex can be a little difficult to understand at first as it’s a different way to store data in your application. The documentation written about Vuex is extremely thorough and helpful: What is Vuex? · Vuex . Not all applications will require a Vuex data store, but if you were like me, and you are writing a larger single page application, you begin to think “there has to be a better way to handle all of this data”. You are right, that’s Vuex.

Step 1: Understanding mutations, getters, actions, modules, and store.

The first thing we have to do is lay out our data plan for what we are trying to build. Right now we have a backend API for cafes in Roast and a front end set of API methods to access these routes. Our fist module we will build will be a cafes module. This module will handle all of the data relating to what we get back from the API for cafes. A module is almost like a a basic object with certain methods used to access and set the data.

Next up, we have mutations. Mutations are ways to manipulate the data. Every piece of data in a module should have mutations there to manipulate it. You will NOT be manipulating the data directly. The goal of Vuex is to have a single funnel for all components to access the state of the data and keep it in sync. You will call an action to mutate the data. If this doesn’t make sense, the code examples in this tutorial will help and this part of the Vuex documentation What is Vuex? · Vuex. The section “What is a “State Management Pattern”?” has some super helpful images that explain the flow of Vuex.

Getters are how you access the current state of the data. You will be using these in the components you create to access the single point of data for your application.

All of the modules live in what’s called a data store. This is like the entry point for your data structure. Each module like cafe will be part of a larger data store.

Lets get started and I’ll explain more as we go!

Step 2: Configure your /resources/assets/js/store.js

So we already created an initial store.js file when we set up our application to work with VueJS. We will need to open this file and a little code so we have an initial store ready to rock and roll.

At the top of your file, import Vue and Vuex so we can start building out our store:

/*
    Imports Vue and Vuex
*/
import Vue from 'vue'
import Vuex from 'vuex'

Next, we will instruct Vue to use Vuex as a data store. This will extend our Vue instance with the methods needed to utilize the Vuex data store. Add this line of code right after you imported Vue and Vuex:

/*
    Initializes Vuex on Vue.
*/
Vue.use( Vuex )

Lastly, we will export a new Vuex data store from our store.js file. This is so we can apply it to our Vue instance and have all of the modules accessible within components and routes. Add this to the end of the file:

/*
  Exports our data store.
*/
export default new Vuex.Store({
    modules: {

    }
});

We now have a very basic data store configured. This will make it easy for us to expand with our modules.

Step 3: Add es6-promise polyfill to store for IE

Of course IE 11 doesn’t support promises and requires extra configuration. We will need to add the polyfill to our store so Vuex will work with IE.

So open up a terminal, navigate to your development directory and run the following command:

npm install es6-promise —save-dev

This will add the es6-promise polypill to your package.json file for your application.

On the top of the /resources/assets/js/store.js file add the following line of code:

/*
  Adds the promise polyfill for IE 11
*/
require('es6-promise').polyfill();

Our store.js file should look like:

/*
|-------------------------------------------------------------------------------
| VUEX store.js
|-------------------------------------------------------------------------------
| Builds the data store from all of the modules for the Roast app.
*/
/*
  Adds the promise polyfill for IE 11
*/
require('es6-promise').polyfill();

/*
    Imports Vue and Vuex
*/
import Vue from 'vue'
import Vuex from 'vuex'

/*
    Initializes Vuex on Vue.
*/
Vue.use( Vuex )

/*
  Exports our data store.
*/
export default new Vuex.Store({
    modules: {

    }
});

We will be adding modules shortly, but it’s nice to get this all set up so we have a place to put our modules.

Step 4: Add the Data Store to Vue

Now that we have our data store built, we will need to add it to Vue. Our Vue instance that we are using resides in /resources/assets/js/app.js so open that file.

Below our import for our router add the following line of code:
import store from './store.js'

This includes our store that we created. Next we have to extend our Vue instance with that store. So right under the router, make sure include the store we just imported. The updated code should look like:

new Vue({
    router,
  store
}).$mount('#app')

Our app.js file should now look like:

window._ = require('lodash');

try {
    window.$ = window.jQuery = require('jquery');

    require('foundation-sites');
} catch (e) {}

/**
 * We'll load the axios HTTP library which allows us to easily issue requests
 * to our Laravel back-end. This library automatically handles sending the
 * CSRF token as a header based on the value of the "XSRF" token cookie.
 */

window.axios = require('axios');

window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';

/**
 * Next we will register the CSRF Token as a common header with Axios so that
 * all outgoing HTTP requests automatically have it attached. This is just
 * a simple convenience so we don't have to attach every token manually.
 */

let token = document.head.querySelector('meta[name="csrf-token"]');

if (token) {
    window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
} else {
    console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
}

import Vue            from 'vue';
import router           from './routes.js'
import store          from './store.js'

new Vue({
    router,
  store
}).$mount('#app')

We now are using the entire VueJS ecosystem in our application! Let’s add some modules and put this work horse to use!

Step 5: Add cafes.js Vuex module

The first part is to create a new file in your /resources/assets/js/modules/ directory named cafes.js. This will be where we manage all of the data for our cafes. We can then use all of this data through out our app. This is where you will start to see some of the benefits of load time with a Single Page App. You will load the cafes once, store it our Vuex module and only re-load when needed. It will be available for use on any page or any component in our app!

Right now, the cafes.js file should be empty, the next step is configuring the file.

Step 6: Configure the State

We are diving heavily into Vuex now! With our /resources/assets/js/modules/cafes.js file open, first import the CafeAPI from our API directory that we built in: https://serversideup.net/build-api-requests-javascript/. We will be using these methods in our actions to load the data.

Our file should look like this:

/*
|-------------------------------------------------------------------------------
| VUEX modules/cafes.js
|-------------------------------------------------------------------------------
| The Vuex data store for the cafes
*/

import CafeAPI from '../api/cafe.js';

Now we will export a constant which will be our cafes module. Underneath the imported CafeAPI add the following code:

export const cafes = {

}

This is our module that we will be adding to our data store. We will import this into the store later on, for now we are just stubbing out our module.

Next we will add the 4 aspects of a Vuex module (state, actions, mutations, getters). First we will add an empty object for the state like so:

export const cafes = {
    state: {

    }
}

The state is all of the data we want to track. In the cafes module I can think of 2 different pieces of data that we would want: An array of cafes, and an object that stores a single cafe. This matches our API that returns all of the cafes and an individual cafe.

We will initialize these two pieces of data like this:

export const cafes = {
    state: {
        cafes: [],
        cafe: {}
    }

As a rule of thumb, one issue I always ran into was displaying a loading state. In a Single Page App, loading states are essential. The HTML/CSS and other page features will load usually before the data giving a bad UX to users who are waiting for the data to load. For each pice of data we track in the state, I add a corresponding variable to the state which keeps track of the load status. This way I can read this variable to determine whether to display the loading state or not. With VueJS being reactive, when the data is loaded, the variable is updated, the components using the variable will be updated as well and show the screen accordingly. I’d add the following to the state:

export const cafes = {
    state: {
        cafes: [],
        cafesLoadStatus: 0,

        cafe: {},
        cafeLoadStatus: 0
    }
}

I usually follow a method like this:
* status = 0 -> No loading has begun
* status = 1 -> Loading has started
* status = 2 -> Loading completed successfully
* status = 3 -> Loading completed unsuccessfully

This way we can display the appropriate information when needed depending on what loaded.

Step 7: Configure the Actions

I briefly went over what actions were, but now it’s time to implement them. Actions are what is called on the module to mutate the state. In this case, we will call an action that makes a request to the API and commits a mutation. The mutations we will implement in the next step.

First we will add an empty object that will contain our action methods:

export const cafes = {
    state: {
        cafes: [],
    cafesLoadStatus: 0,

    cafe: {},
    cafeLoadStatus: 0
    },

    actions: {

    }
}

In our actions object we will add the methods to load our cafes and individual cafe. Our module should look like:

export const cafes = {
    state: {
        cafes: [],
    cafesLoadStatus: 0,

    cafe: {},
    cafeLoadStatus: 0
    },

    actions: {
        loadCafes( { commit } ){

        },
        loadCafe( { commit }, data ){

        }
    }
}

Two things to note.
1. Each method contains a destructured argument called commit. This is passed in by Vuex and allows us to commit mutations for our store. There are other destructured arguments you can pass in as well. To read more about Argument Destructuring read: GitHub – lukehoban/es6features: Overview of ECMAScript 6 features
2. The loadCafe action contains a second argument named data. This is an object we will pass to the method that contains the ID of the cafe we are loading. We are limited to an extra argument so you can pass in an object for more variables.

Now to implement these methods, we will do the following:

export const cafes = {
    state: {
        cafes: [],
    cafesLoadStatus: 0,

    cafe: {},
    cafeLoadStatus: 0
    },

    actions: {
        loadCafes( { commit } ){
      commit( 'setCafesLoadStatus', 1 );

      CafeAPI.getCafes()
        .then( function( response ){
          commit( 'setCafes', response.data );
          commit( 'setCafesLoadStatus', 2 );
        })
        .catch( function(){
          commit( 'setCafes', [] );
          commit( 'setCafesLoadStatus', 3 );
        });
    },

    loadCafe( { commit }, data ){
      commit( 'setCafeLoadStatus', 1 );

      CafeAPI.getCafe( data.id )
        .then( function( response ){
          commit( 'setCafe', response.data );
          commit( 'setCafeLoadStatus', 2 );
        })
        .catch( function(){
          commit( 'setCafe', {} );
          commit( 'setCafeLoadStatus', 3 );
        });

    }
    },
}

First thing to note is the commit function. This is committing a mutation. We will be setting up these mutations in the next step. However, just a reminder, each piece of data in the state should have a mutation. In both methods, we commit the load status for the piece of the state we are using. Next we make an API call to load the specific piece of information that we want to load. These API calls are defined in our /resources/assets/js/api/cafe.js file. Since we are returning axios (GitHub – axios/axios: Promise based HTTP client for the browser and node.js) calls, we can then bind into the then and catch chained promises. then method gets called when the method is returned successfully, so we will commit a mutation with the data returned. The catch method gets called when the data has been loaded unsuccessfully. We then will mutate the data accordingly like set the load statuses to failure and clear out the cafes or the state since we don’t want failed data. The response variable passed into each of the method allows us to access the response data and headers from the request.

Step 8: Configure the Mutations

The mutations are how your data gets updated. Each module has state, which requires a mutation to update. The flow is like this:

  1. The user calls an action
  2. The action loads/computes data
  3. The action commits a mutation
  4. The state gets updated
  5. The getter (next step) reactively returns the state back to the component.
  6. The component gets updated.

There are lots of steps but trust me, compared to implementing something like this in jQuery or vanilla JS, Vuex makes this a breeze.

We have our state built, our actions made, now it’s time to implement the mutations. We saw mutations being called when we configured our actions, time to make them functional.

If you look at the uniqueness of the commit methods in our actions, there are 4 unique mutations needed:

  1. setCafesLoadStatus
  2. setCafes
  3. setCafeLoadStatus
  4. setCafe

To start, let’s add these methods to our mutations object:

mutations: {
    setCafesLoadStatus( state, status ){

    },

    setCafes( state, cafes ){

    },

    setCafeLoadStatus( state, status ){

    },

    setCafe( state, cafe ){

    }
},

So all mutations do is set the state. This way they can be tracked and it’s consistent whenever the state needs to be updated. The first argument for the mutation is the state. This is the local module state NOT the global state. So the state we configured in step 6 is accessible. The second parameter is the data we need to update the state to. Our mutations should look like:

mutations: {
    setCafesLoadStatus( state, status ){
      state.cafesLoadStatus = status;
    },

    setCafes( state, cafes ){
      state.cafes = cafes;
    },

    setCafeLoadStatus( state, status ){
      state.cafeLoadStatus = status;
    },

    setCafe( state, cafe ){
      state.cafe = cafe;
    }
},

In each mutation, we set the local module’s state data to what is being passed in. Really this is all each mutation does, mutates the state data. Next, we will configure the getters and our first Vuex module will be ready to go!

Step 9: Configure the Getters

So we have state data we want to track, actions to retrieve the data from the API and mutations to set the data. Now it’s time to retrieve the data from the modules. We do that with getters. We need to define an object that will contain all of our getter functions. If you are used to some OOP design principals, mutations are like setters, getters are… getters, they retrieve the data.

Our getters object should be added to our cafes component like:

getters: {

}

Next we will need a method for each of our state variables that will return the state data we are looking for:

getters: {
    getCafesLoadStatus( state ){
      return state.cafesLoadStatus;
    },

    getCafes( state ){
      return state.cafes;
    },

    getCafeLoadStatus( state ){
      return state.cafeLoadStatus;
    },

    getCafe( state ){
      return state.cafe;
    }
}

Each method takes the local module state as the parameter and each method returns the corresponding data in the state we wish to retrieve. That’s all we need to do for our getters! We can now utilize all of this data within our components.

Our module is now complete for this tutorial and should look like this:

/*
|-------------------------------------------------------------------------------
| VUEX modules/cafes.js
|-------------------------------------------------------------------------------
| The Vuex data store for the cafes
*/

import CafeAPI from '../api/cafe.js';

export const cafes = {
  /*
    Defines the state being monitored for the module.
  */
    state: {
        cafes: [],
    cafesLoadStatus: 0,

    cafe: {},
    cafeLoadStatus: 0
    },

  /*
    Defines the actions used to retrieve the data.
  */
    actions: {
    /*
      Loads the cafes from the API
    */
        loadCafes( { commit } ){
      commit( 'setCafesLoadStatus', 1 );

      CafeAPI.getCafes()
        .then( function( response ){
          commit( 'setCafes', response.data );
          commit( 'setCafesLoadStatus', 2 );
        })
        .catch( function(){
          commit( 'setCafes', [] );
          commit( 'setCafesLoadStatus', 3 );
        });
    },

    /*
      Loads an individual cafe from the API
    */
    loadCafe( { commit }, data ){
      commit( 'setCafeLoadStatus', 1 );

      CafeAPI.getCafe( data.id )
        .then( function( response ){
          commit( 'setCafe', response.data );
          commit( 'setCafeLoadStatus', 2 );
        })
        .catch( function(){
          commit( 'setCafe', {} );
          commit( 'setCafeLoadStatus', 3 );
        });

    }
    },

  /*
    Defines the mutations used
  */
    mutations: {
    /*
      Sets the cafes load status
    */
    setCafesLoadStatus( state, status ){
      state.cafesLoadStatus = status;
    },

    /*
      Sets the cafes
    */
    setCafes( state, cafes ){
      state.cafes = cafes;
    },

    /*
      Set the cafe load status
    */
    setCafeLoadStatus( state, status ){
      state.cafeLoadStatus = status;
    },

    /*
      Set the cafe
    */
    setCafe( state, cafe ){
      state.cafe = cafe;
    }
    },

  /*
    Defines the getters used by the module
  */
    getters: {
    /*
      Returns the cafes load status.
    */
    getCafesLoadStatus( state ){
      return state.cafesLoadStatus;
    },

    /*
      Returns the cafes.
    */
    getCafes( state ){
      return state.cafes;
    },

    /*
      Returns the cafes load status
    */
    getCafeLoadStatus( state ){
      return state.cafeLoadStatus;
    },

    /*
      Returns the cafe
    */
    getCafe( state ){
      return state.cafe;
    }
    }
}

Step 10: Add the Module to the Store

Last but not least, we need to tell our Vuex data store to use the cafe module. To do this we need to open our /resources/assets/js/store.js file first.

Right below telling Vue to use Vuex, add the following:

/*
    Imports all of the modules used in the application to build the data store.
*/
import { cafes } from './modules/cafes.js'

This imports our cafes module, we are now ready to register it with the store. To do that, simply add the cafes to the Vuex.Store({}) modules object. Our store.js file should look like:

/*
|-------------------------------------------------------------------------------
| VUEX store.js
|-------------------------------------------------------------------------------
| Builds the data store from all of the modules for the Roast app.
*/
/*
  Adds the promise polyfill for IE 11
*/
require('es6-promise').polyfill();

/*
    Imports Vue and Vuex
*/
import Vue from 'vue'
import Vuex from 'vuex'

/*
    Initializes Vuex on Vue.
*/
Vue.use( Vuex )

/*
    Imports all of the modules used in the application to build the data store.
*/
import { cafes } from './modules/cafes.js'

/*
  Exports our data store.
*/
export default new Vuex.Store({
    modules: {
    cafes
    }
});

Wrapping Up

In this tutorial we created a Vuex store and configured a module for our cafes. To see this in the application, visit your development environment and open the developer tools. Switch to the Vue tab (assuming you are using a supported browser) and it should look something like this:

You can see the state, the module and the getters within. The left side will show all of the mutations run and even allow you to time travel back and forth through your state. The entire Vue ecosystem is beautiful and the developer tools is the icing on the cake! The next tutorial will include a little bit of styling and some functionality on the front end of the app! Make sure to check out the code base here:
GitHub – serversideup/roastandbrew

Keep Reading
View the course View the Course API Driven Development With Laravel and VueJS
Up Next → Using Vuex Modules Inside Components

Support future content

The Ultimate Guide to Building APIs and Single-Page Applications with Laravel + VueJS + Capacitor book cover.

Psst... any earnings that we make off of our book is being reinvested to bringing you more content. If you like what you read, consider getting our book or get sweet perks by becoming a sponsor.

Written By Dan

Dan Pastori avatar Dan Pastori

Builder, creator, and maker. Dan Pastori is a Laravel certified developer with over 10 years experience in full stack development. When you aren't finding Dan exploring new techniques in programming, catch him at the beach or hiking in the National Parks.

Like this? Subscribe

We're privacy advocates. We will never spam you and we only want to send you emails that you actually want to receive. One-click unsubscribes are instantly honored.