Home

Awesome

vuex-rest-api

npm npm

A Helper utility to simplify the usage of REST APIs with Vuex 2. Uses the popular HTTP client axios for requests. Works with websanova/vue-auth.

Table of Contents

What is this good for

If you want to connect a REST API with Vuex you'll find that there are a few repetitive steps. You need to request the data from the api (with an action) and set the state (via a mutation). This utility (for the sake of brevity called Vapi in the README) helps in creating the store by setting up the state, mutations and actions with a easy to follow pattern.

It is not a middleware.

It's just a helper utility to help prepraring the store object for you. If there's something you don't like just overwrite the property.

Installation

npm install vuex-rest-api

Some notes: This readme assumes that you're using at least ES2015.

Steps

  1. Import vuex-rest-api (I called it Vapi).
  2. Create a Vapi instance.
    At least you have to set the base URL of the API you're requesting from. You can also define the default state. If you don't define a default state from a property it will default to null. In the example
  3. Create the actions.
    Each action represents a Vuex action. If it will be called (property action), it requests a specific API endpoint (property path) and sets the related property named property to the response's payload.
  4. Create the store object
  5. Pass it to Vuex. Continue reading here to know how to call the actions.
// store.js

import Vuex from "vuex"
import Vue from "vue"
// Step 1
import Vapi from "vuex-rest-api"

Vue.use(Vuex)

// Step 2
const posts = new Vapi({
  baseURL: "https://jsonplaceholder.typicode.com",
    state: {
      posts: []
    }
  })
  // Step 3
  .get({
    action: "getPost",
    property: "post",
    path: ({ id }) => `/posts/${id}`
  })
  .get({
    action: "listPosts",
    property: "posts",
    path: "/posts"
  })
  .post({
    action: "updatePost",
    property: "post",
    path: ({ id }) => `/posts/${id}`
  })
  // Step 4
  .getStore()

// Step 5
export const store = new Vuex.Store(posts)

API

The following sections explain the API of the Vapi class.

constructor(options:Object):Vapi

Creates a new Vapi instance and returns it.

const vapi = new Vapi(options)

The parameter options consists of the following properties:

# axios

# baseURL

{
  baseURL: "https://jsonplaceholder.typicode.com"
}

# queryParams

{
  queryParams: true
}

# state

{
  state: {
    post: null, // this is unnecessary (default is null)
    posts: []
  }
}

Sets post to null and posts to an empty array.

add(options):Vapi

Adds an action to access an API endpoint and returns the Vapi instance.

The parameter options consists of the following properties:

# action (required)

{
  action: "getPosts"
}

# method

{
  method: "get"
}
shorthand syntax

You can also use the http method instead of add to omit to set the options.method like this. This works with get, delete, post, put and patch:

// regular way
vrap.add({
  method: "delete"
  // other options...  
})

//shorthand
vrap.delete({
  // other options...
})

# property

{
  property: "posts"
}
When to set property in spite of it's optionality

Sometimes you have to set the state by yourself. In that case you may consider to avoid setting property. Please consider the following consequences:

# path (required)

Usage with a function

Here we pass a custom path function. This is necessary, because we also need to pass an id. Please note that id is passed in an object. This is necessary because you could pass multiple arguments.

{
  path: ({id}) => `/post/${id}`
}
Usage with a string

Maybe the API endpoint needs no parameters. Then you can use a string like this:

{
  path: "/posts"
}

# headers

Usage with a function

Here we pass a custom headers function. This allows us to change the extra headers for each request. The data has to be passed over the params bag.

Please note that headers provided via this property will override it's counterpart you maybe set via requestConfig.headers.

// setting the header
{
  headers: ({foo, bar}) => ({
      "FOO": foo,
      "BAR": bar
    })
}

// calling the mapped action and passing the data over the params object
this.getPosts({
  params: { 
    foo: "foo-header",
    bar: "bar-header"
  } 
})
Usage with a string

If the headers don't have to be evaluted on every request, just pass them via an object. Alternatively you could also set the headers via the requestConfig.headers property.

{
  headers: {
    "FOO": "foo-header",
    "BAR": "bar-header"
  }
}

# beforeRequest

{
  beforeRequest: (state, { params, data }) => {
    state.posts = state.posts.filter(post => post.id !== params.id)
  }
}

# onSuccess

{
  onSuccess: (state, payload, axios) => {
    // if you set the onSuccess function you have to set the state manually
    state.posts = payload.data
    state.post = payload.data[0]
  }
}

# onError

{
  onError: (state, error, axios) => {
    Toast.showError(`Oops, there was following error: ${error}`)

    // if you set the onError function you have to set the state manually
    state.post = null
  }
}

# requestConfig

{
  requestConfig: {
    //excerpt from (https://github.com/mzabriskie/axios#request-config)
    // `paramsSerializer` is an optional function in charge of serializing `params`
    // (e.g. https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param/)
    paramsSerializer: function(params) {
      return Qs.stringify(params, {arrayFormat: 'brackets'})
    },
  }
}

# queryParams

{
  queryParams: true
}

getStore(options):Object

Creates an object you can pass to Vuex to add a store.

The parameter options consists of the following properties:

# createStateFn

{
  createStateFn: false
}

The returned object looks like this if you would call it with the settings of the example:

{
  state: {
    pending: {
      posts: false,
      post:  false
    },
    error: {
      posts: null,
      post:  null
    },
    posts:   [],
    post:    null
  }),
  mutations: {
    LIST_POSTS:            Function,
    LIST_POSTS_SUCCEEDED:  Function,
    LIST_POSTS_FAILED:     Function,
    GET_POST:              Function,
    GET_POST_SUCCEEDED:    Function,
    GET_POST_FAILED:       Function,
    UPDATE_POST:           Function,
    UPDATE_POST_SUCCEEDED: Function,
    UPDATE_POST_FAILED:    Function
  },

  //every action's signatures is function(params, data)
  actions: {
    listPosts:  Function,
    getPost:    Function,
    updatePost: Function
  }
}

As you can see, it just created the store for us. No more, no less.

Miscellaneous

Calling the actions

Please respect the following function signature if you want to call an action:

// direct via store
this.$store.dispatch("actionName", { params: {}, data: {} })

// or with mapActions
this.actionName({ params: {}, data: {} })`

params (optional) represents the data you passed to the path property. data (optional) is your request's payload.

Please note that you do not have to set params, data or the enclosing object if you don't need them.

Examples:

// direct via store
this.$store.dispatch("listPosts")

// or with mapActions
this.listPosts()
// direct via store
const params = { id: 42 }
this.$store.dispatch("getPost", { params })

// or with mapActions
this.getPost({ params })
// direct via store
const params = { id: 42 }
const data = { content: "foobar" }
this.$store.dispatch("actionName", { params, data })

// or with mapActions
this.actionName({ params, data })

Query params

If you want to use query params just set the queryParams property either in the constructor or the options from the add method. If you need it for just one action set it in the corresponding method. On the other hand, if you need it for all actions, set it in the constructor.

Please note that the method's queryParams property is more specific than the constructor's. So if you set queryParams in a method's options it will override the queryParams value of the constructor option!

Params will also be appended to the URL if you set a paramsSerializer function in the requestConfig property of the add method or if you pass an axios instance with set paramsSerializer function in the Resource constructor.

Add additional state, actions and mutations to the store

As mentioned before, vuex-rest-api is just creating a regular store object. So you can add arbitrary actions, mutations and state properties to the store as written in the Vuex documentation:

// resource creation hidden for the sake of brevity

// create the store
posts = postsResource.getStore()

// add a simple counter to the store
posts.state.counter = 0
posts.mutations.increment = state => {
  state.counter++
}
posts.actions.increment = context => {
  context.commit("increment")
}

Usage with websanova/vue-auth

If you want to use this little helper with vue-auth you don't have to do anything. It will just work due to the fact that vue-auth uses the global axios instance. Don't pass any axios instance to Vapi and it will work.

Check error and loading state of requests

vuex-rest-api sets for every property set in the add() method two different states:

This is really handy if you want to show a loading hint for specific requests or an error message:

<template>
  <div>
    <ul>
      <li v-for="post in posts">{{post}}</li>
    </ul>
    <p v-if="pending.posts">loading posts...</p>
    <p v-if="error.posts">loading failed</p>
  </div>
</template>

<script>
import { mapState, mapActions } from 'vuex'

export default {
  created() {
    this.getPosts()
  },

  // make states available
  computed: mapState({
    posts: state => state.posts.posts,
    pending: state => state.pending,
    error: state => state.error
  }),
  methods: {
    ...mapActions([
      "getPosts"
    ])
  }
}
</script>

Changelog

see CHANGELOG.md