Ukpai Ugochi I'm a full-stack JavaScript developer on the MEVN stack. I love to share knowledge about my transition from marine engineering to software development to encourage people who love software development and don't know where to begin. I also contribute to OSS in my free time.

How to consume APIs with Vuex, Pinia, and Axios

7 min read 2230

How to consume APIs with Vuex, Pinia, and Axios

Editor’s note: This post was updated 11 March 2022 to include the same Vuex tutorial sections for Pinia, following news that Vuex and Pinia will soon merge.

An application programming interface (API) is a set of programming standards for accessing an application. This means that with an API, your backend and frontend applications can communicate with each other without the knowledge of the user.

“Consuming” an API means to receive requests and send responses via an API. In this article, you’ll learn how to consume APIs from a server using Vuex, Pinia, and Axios. We’ll cover the following sections:

Using Axios with Vue

Some frameworks, such as Angular, JQuery, and version 1.0 of Vue.js, came with inbuilt HTTP APIs. Since Vue 2.0, the developers decided that the inbuilt HTTP client module, Vue-resource, was no longer essential and could be replaced by third-party libraries. Now, the most recommended library is Axios.

Axios is a flexible HTTP client library that uses promises by default and runs on both the client and the server, which makes it appropriate for fetching data during server-side rendering. It’s easy to use with Vue.js.

What is Vuex?

Passing props can be difficult or almost impossible for deeply-nested components. It can be tempting to use direct references to parent/child instances, or try to mutate and synchronize multiple copies of the state via events. But neither of these options are recommended because they may produce unmaintainable code and bugs.

Vuex is a state management pattern and library for Vue.js applications. It serves as a centralized store for all the components in an application.

The Vuex state management cycle through mutations, getters, components, and actions
The Vuex state management cycle through mutations, state, getters, components, and actions

What is Pinia?

Similar to Vuex, Pinia is another state management library designed to work with the Vue framework. Some of Pinia’s underlying concepts and principles are quite similar to Vuex’s and only a few concepts differentiate these libraries, such as out-of-the-box modularity and a slight difference in syntax. With sufficient knowledge of Vuex, learning to use Pinia should be an easy switch if you’re just getting started.

Despite being built with the Composition API and Vue 3 in mind, Pinia works with both Vue versions 2 and 3, regardless of whether you intend to use the Composition API or the Options API.

Understanding how state management works

State management is the implementation of a design pattern to manage data that will be shared among various components in a given application, which helps to ensure consistency across data that is being presented to the user.



In a Vue.js single-page application (SPA), multiple components may, at any given time, interact with and change the data before sending it back to the server. Therefore, developers need a way to manage these changes — “the state” — which can be done with a state management library such as Vuex or Pinia.

A state management library lets you extract shared state from components, manage it in a global singleton, and either provide components access to the state, or trigger actions, no matter where they are in the tree.

When to use a state management library

If you have never built a large-scale SPA, using a library for state management may feel too complicated and unnecessary. But if you are building a medium- to large-scale SPA, chances are you have run into situations that have made you think twice about how to better handle and share state between components. If so, Vuex or Pinia is the right tool for you!

Why you should use a state management library

Typically, you’ll want to use a library to manage state because:

  • Your components need to share and update state
  • They provide a single source of truth for data/state
  • There’s no need to pass events from one component down through multiple components when you have state management
  • Global state is reactive, which means altering state allows it to be updated in every other component using it

Set up a new Vue project with Axios

For this tutorial, we’ll be working with the latest default Vue version, 3.0, and the Composition API. If you don’t have the Vue CLI installed already, run the following in your terminal:

npm install -g @vue/cli

To see if the Vue CLI is already installed on your system, execute the code below. A Vue CLI version should appear if Vue is already installed.

vue --version
// 5.0.1

Now, create a Vue project by running the following code in your terminal:

/** if you want to create the vue project in the folder you
are currently on, use this code**/
vue create .

/**if you want to create a new folder for your vue app,
you should use this code**/
vue create my-app

Next, you’ll receive a prompt to select a Vue version; select Vue 3.

In the terminal, select the appropriate Vue version

Once our app is ready, follow the commands to move into the app directory and start the server:


More great articles from LogRocket:


Change into the app directory to start the server

Install and set up Axios

You can install and set up Axios which we’ll be using to fetch data from the API with the following code:

# Using yarn
yarn add axios
# Using npm
npm install axios

Install and set up a Vuex store

Install and setup Vuex with the following code:

# Using yarn
yarn add vuex
# Using npm
npm install vuex

Next, create a folder in your src folder and name it store. In your store folder, create a file and name it index.js.

├── index.html
└── src
    ├── components
    ├── App.vue
    └──store
       └── index.js        # where we assemble modules and export the store

Next, in your store/index.js file, input the following code:

//import the createStore object from Vuex
import { createStore } from 'vuex'
// Import axios to make HTTP requests
import axios from "axios"
export default createStore({
    state: {},
    getters: {},
    actions: {},
    mutations: {}
})
/** we have just created a boiler plate for our vuex store module**/

Register your store by adding the following code to your main.js file:

import { createApp } from 'vue'
import App from './App.vue'
//Add the line below to the file
import store from "./store";


createApp(App)
//Add the line below to the file
.use(store)
.mount('#app')

//other lines have already been added to your main.js file by default

Install and set up a Pinia store

To install and setup Pinia, follow the steps below:

# First install the package
yarn add pinia
# or with npm
npm install pinia

Next, import Pinia in the root file of your app, the main.js file, and instantiate it as such:

import { createApp } from 'vue'
import App from './App.vue'

//Import Pinia into your config file
import { createPinia } from 'pinia'

createApp(App)
//Add the line below to the file to instantiate it
.use(createPinia())
.mount('#app')

Next, create a stores folder in the root of the src folder and add a file named users.js.

// stores/users.js
import { defineStore } from 'pinia'
// Import axios to make HTTP requests
import axios from "axios"

export const useUserStore = defineStore("user",{
    state: () => ({}),
    getters: {},
    actions: {},
})

We’ve successfully setup both our Vuex and Pinia stores! Now, let’s get into using our stores to fetch data from an API to populate our template.

Consuming APIs with Vuex and Axios

Before we proceed, if you’d like to learn more about actions, mutations, and getters in Vuex, I recommend checking out LogRocket’s really concise article on the subject.

To properly understand how to create a Vuex store and consume APIs with Axios using Vuex actions, state, mutations, and getters, we’ll create a simple Vuex application that fetches user information from our fake JSON backend and populates our template:

// store/index.js
import { createStore } from 'vuex'
import axios from 'axios'
export default createStore({
    state: {
        users: []
    },
    getters: {
      getUsers: (state) => state.users
    },
    actions: {
      async fetchUsers({ commit }) {
          try {
            const data = await axios.get('https://jsonplaceholder.typicode.com/users')
              commit('SET_USERS', data.data)
            }
            catch (error) {
                alert(error)
                console.log(error)
            }
        }
    },
    mutations: {
        SET_USERS(state, users) {
          state.users = users
      }
    }
})

We’re using the async/await format to fetch data from the endpoint, and using Axios to make the HTTP request.

We’ll make a request to the API form Inside of the actions object. In the try block, we then assign a data variable to our HTTP request; when a response is returned, a mutation is committed, which in turn updates the application’s state.

We can also alert on errors or log them in the console in the catch block.

In your HelloWorld.vue file, paste the following code:

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <h1>Made By Getters</h1>
  <div v-for='gettersUser in gettersUsers' :key='gettersUser.id'>
    {{gettersUser.id}} {{gettersUser.name}} {{gettersUser.address}}
    </div>
    <h1>Made By Actions</h1>
  <div v-for='user in users' :key='user.id'>
    {{user.id}} {{user.name}} {{user.address}}
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, computed } from 'vue';
//import the global store object from Vuex
import {useStore} from 'vuex'
// declare the store variable
const store = useStore();

const msg = ref("Welcome to my Vuex Store");

const getUsers = computed(() => {
  return store.getters.getUsers
})

const users = computed(() => {
  return store.state.users
})

onMounted(() => {
// dispatch the fetchUsers action which commits a mutation to update the state
  store.dispatch("fetchUsers");
})
</script>

Navigate back to localhost:8080 on your browser to see the changes we’ve made:

Navigate to localhost 8080 to see the changes we made in the browser

Consuming APIs with Pinia and Axios

We’re going to replicate the output in the Vuex example using Pinia and Axios:

//stores/users.js

import { defineStore } from 'pinia'
// Import axios to make HTTP requests
import axios from "axios"
export const useUserStore = defineStore("user", {
    state: () => ({
        users: [],
    }),
    getters: {
      getUsers(state){
          return state.users
        }
    },
    actions: {
      async fetchUsers() {
        try {
          const data = await axios.get('https://jsonplaceholder.typicode.com/users')
            this.users = data.data
          }
          catch (error) {
            alert(error)
            console.log(error)
        }
      }
    },
})

Replace the code in the HelloWorld.vue file with the following:

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <h1>Made By Getters</h1>
  <div v-for='gettersUser in getUsers' :key='gettersUser.id'>
    {{gettersUser.id}} {{gettersUser.name}} {{gettersUser.address}}
    </div>
    <h1>Made By Actions</h1>
  <div v-for='user in users' :key='user.id'>
    {{user.id}} {{user.name}} {{user.address}}
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue';
//import users store
import { useUserStore } from "../stores/users";
// declare store variable
const store = useUserStore();

const msg = ref("Welcome to my Vuex Store");

const getUsers = computed(() => {
  return store.getUsers
})
const users = computed(() => {
  return store.users
})

onMounted(() => {
  store.fetchUsers();
})
</script>

In Pinia, stores are modular by default. For each created store, we need to import the store in the component it is to be used.

With that, we import our useUserStore and assign it to the store variable to be easily accessible throughout our component.

Instead of the store.dispatch("fetchUsers") method in Vuex, in Pinia, an action is seen as a function and can be accessed from the declared store variable using the store.fetchUsers() method.

Different Vuex store structures

Below is the simplest Vuex store structure, where actions, getters, state, and mutations are called and exported in the index.js file.

NB: Pinia is modular out of the box, so these Vuex examples might not apply.

├── index.html
└── src
    ├── components
    ├── App.vue
    └──store
       └── index.js         # where we assemble modules and export the store

As your application becomes larger, you may need to separate your actions, mutations, getters, and state modules into their own, different files.

├── index.html
└── src
    ├── components
    ├── App.vue
    └──store
       ├── index.js        # where we assemble modules and export the
       ├── actions.js      # root actions
       ├── mutations.js    # root mutation
       ├── getters.js      # root getters
       └── state

All states in our application are contained inside one large object because we’re using a single state tree. This isn’t ideal because our application grows as our actions, mutations, and state become larger, making it possible for the store to become even larger, so much so that we may not be able to keep up with it.

A great solution is to group our store into modules. Each module can contain its own state, mutations, actions, and getters — and they can even contain nested modules, providing greater options for scalability.

├── index.html
├── main.js
├── api
│   └── ... # abstractions for making API requests
├── components
│   ├── App.vue
│   └── ...
└── store
    ├── index.js          # where we assemble modules and export the store
    ├── actions.js        # root actions
    ├── mutations.js      # root mutations
    └── modules
        ├── cart.js       # cart module
        └── products.js

Conclusion

In this tutorial, we’ve looked at what state management is and why you need a library such as Vuex/Pinia to handle state for your Vue applications.

In addition, we created a simple Vue application to demonstrate Vuex/Pinia, as well as learned different store structures so you can decide which one is best for your application.

Experience your Vue apps exactly how a user does

Debugging Vue.js applications can be difficult, especially when there are dozens, if not hundreds of mutations during a user session. If you’re interested in monitoring and tracking Vue mutations for all of your users in production, try LogRocket. https://logrocket.com/signup/

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens in your Vue apps including network requests, JavaScript errors, performance problems, and much more. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.

The LogRocket Vuex plugin logs Vuex mutations to the LogRocket console, giving you context around what led to an error, and what state the application was in when an issue occurred.

Modernize how you debug your Vue apps - .

Ukpai Ugochi I'm a full-stack JavaScript developer on the MEVN stack. I love to share knowledge about my transition from marine engineering to software development to encourage people who love software development and don't know where to begin. I also contribute to OSS in my free time.

8 Replies to “How to consume APIs with Vuex, Pinia, and Axios”

  1. Hi, how do you handle api errors? How are they transported to the GUI when all handling takes place in the store?

  2. Hello! I mostly use bootstrap-vue dismissible alert (https://bootstrap-vue.org/docs/components/alert). This is an example of how I use it.

    {{error}}

    export default {
    name: “Login”,
    data() {
    return {
    email: “”,
    password: “”,
    errors: [],
    };
    },
    methods: {
    login() {
    this.$store.dispatch(“retrieveUser”, {
    email: this.email,
    password: this.password
    });
    this.$store.dispatch(“retrieveId”, {
    email: this.email,
    password: this.password
    });
    this.$store
    .dispatch(“retrieveToken”, {
    email: this.email,
    password: this.password
    })
    .then(response => {
    const errors = response.data.error;
    const token = response.data.token;
    if (errors) {
    this.errors.push(errors);
    } else
    this.$router.push({
    name: “Home”
    });
    });
    },
    }

    I hope this helps!

  3. “`
    {{gettersUser.id}} {{gettersUser.name}} {{gettersUser.address}} “`
    This makes no sense. It should be getUsers.
    “`
    {{ user.id }} {{ user.name }} {{ user.address }}
    “`

  4. Hi, is there a reason that in the same file in the getters you use state.users and in the actions you use this.users? It is confusing, and not interchangeable, I tried. Actions didn’t work with state.users. Thank you

Leave a Reply