Skip to content

ecamp/hal-json-vuex

Repository files navigation

hal-json-vuex

npm version Downloads Build Status Coverage Status

A package to access HAL JSON data from an API, using a Vuex store, restructured to make life easier.

With this plugin, you can use your HAL JSON API in a fluid way:

// Reading data and traversing relationships
let singleBook = this.api.get('/books/1')
let firstBookName = this.api.get().books().items[0].name // visits the 'books' rel on the root API endpoint
let author = singleBook.author() // related entity
let bookChapters = this.api().books().items[0].chapters()
author = singleBook.author() // doesn't trigger another network call because we already fetched it
this.api.reload(author) // force re-fetching an entity or a URI

// Writing data
this.api.post('/books', { name: 'My first book', author: { _links: { self: '/users/433' } } })
this.api.patch(singleBook, { name: 'Single book - volume 2' })
this.api.del(author).then(() => { /* do something */ })

This library will only load data from the API when necessary (i.e. if the data is not yet in the Vuex store). It also supports templated links and partially loaded data from the API.

Installation

npm install hal-json-vuex

Usage

import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
import HalJsonVuex from 'hal-json-vuex'

Vue.use(Vuex)

const store = new Vuex.Store({})

axios.defaults.baseURL = 'https://my-api.com/api'

Vue.use(HalJsonVuex(store, axios, { /* options */ }))
// Use it in a computed or method or lifecycle hook of a Vue component
let someEntity = this.api.get('/some/endpoint')
this.api.reload(someEntity)
<!-- Use it in the <template> part of a Vue component -->
<li v-for="book in api.get('/all/my/books').items" :key="book._meta.self">...</li>

Nuxt.js (experimental support)

To install in a Nuxt.js application:

// First, make sure the Nuxt.js app uses Vuex, by adding an index.js to your store/ directory.

// Then, create a file plugins/hal-json-vuex.js with the following content:
import Vue from 'vue'
import HalJsonVuex from 'hal-json-vuex'

export default function ({ store, $axios }, nuxtInject) {
  if (!Vue.$api) {
    Vue.use(HalJsonVuex(store, $axios, { nuxtInject }))
  }
}

// Add the plugin to nuxt.config.js:
export default {
  plugins: [
    { src: '~/plugins/hal-json-vuex.js' }
  ],
  // ...
}

Then, you can use $api on both the server side and the client side:

// On the server
async asyncData({ $api }) {
  const books = await $api.get().books()._meta.load
  return { books }
}
// On the client, in a computed or method or lifecycle hook of a Vue component
let someEntity = this.$api.get('/some/endpoint')
<!-- On the client, in the <template> part of a Vue component -->
<li v-for="book in $api.get('/all/my/books').items" :key="book._meta.self">...</li>

Available options

apiName

This package will install a module into your Vuex store, as well as an accessor (this.api) into your Vue prototype. These are by default called api, but in case you want to change that or need to support multiple APIs at the same time, you can use the apiName option:

Vue.use(HalJsonVuex(store, axios, { apiName: 'backend' }))

// In a Vue component
let someEntity = this.backend.get('/some/endpoint')

avoidNPlusOneRequests

When accessing the elements of an embedded collection, and some of the elements of the collection have not been loaded from the API before, this library will automatically try to avoid N+1 queries by eager fetching the whole collection. In case you run into problems with this behaviour with your API, you can disable it by setting the avoidNPlusOneRequests option to false:

Vue.use(HalJsonVuex(store, axios, { avoidNPlusOneRequests: false }))

forceRequestedSelfLink

When requesting an entity, some HAL JSON APIs will not always return the same self link as it was in the request. An example would be if the API added a page=0 query parameter to the self link of a collection, even if the request was done without that parameter:

// request
GET /all/my/books

// response JSON from the API
{
  "_embedded": {
    "items": [ ... ]
  },
  "_links": {
    "self": {
      "href": "/all/my/books?page=0"
    }
  }
}

This can lead to problems, because in your component template you might be requesting /all/my/books but that URI never appears in your Vuex store, causing an infinite loop of re-fetching the same URI.

In case your backend API does this, you can set the forceRequestedSelfLink option to true, and the top-level self link in all responses will be overwritten to the link that was actually requested.

Vue.use(HalJsonVuex(store, axios, { forceRequestedSelfLink: true }))