@prorenata/vue rest resource

Rest resource management for Vue.js and Vuex projects

Version: 1.1.7 Updated: 03/17/2021

By: ProReNata License: MIT

Downloads Last 30 Days: 213

Travis status Dependency status devDependency status npm version

VueRestResource

VueRestResource is a Rest HTTP resource management for Vue.js and Vuex projects. This library integrates Vuex and the server data in a powerful way.

The goal of this library is to reduce boilerplate, simplify the synchronisation between server and client data (in Vuex), take some decisions so the workflow is more predictable.

Example:

(see demo here)

computed: {
  ...asyncResourceGetter('currentUser', UserResource, 'this.myUserIdProp')
},

This line is like a Vuex mapGetter, only it does some async magic and gets the data from the server for you. Its reactive to myUserIdProp and will get the data from Vuex in first hand. If Vuex does not have the data it will get it from the server, put it in Vuex for you, and give it back to the new computed property currentUser created by the asyncResourceGetter.

The UserResource argument is the VRR configuration for that resource. Check the demo here to see it working and how things integrates together.

Configuration

Setup The Store

VRR requires a set of Vuex actions, methods, getters and state to work. These will be automatically generated in the store that you provide, using namespaced modules.

Setup VRR

// plugins/vrr.js

import VRR from 'VueRestResource';
import store from 'your/vuex/store/instance';

const RestConfig = {
  baseUrl: '/api/path', // optional, depends on server configuration
  httpHeaders: {'X-CSRFToken': window.myPrivateToken}, // optional
  store: store, // Vuex store
};

const vrrAPI = vrr.createVueRestResource(RestConfig);

export default vrrAPI;

Extra options with their defaults:

logEndpoints: true,
logInstance: true,
vrrModuleName: MODULE_NAME || 'VRR',
errorHandler: (err) => console.log('VRR error, logging to the console since no handler was provided.', err),

This will return an object from which you can use:

  • the HTTP class to do direct Axios/Ajax requests with Promises
  • the registerResource factory function taking the resource object as argument

VueRestResource will register a module "VRR" in the store (configurable) so we can keep track of open requests.

Setup Error logging

You should replace the default error handler so you can catch it in a try catch when using VRR.

errorHandler: (error) => {
  throw error;
};

Then whenever making a request, you can react to the error. This contains VRR info as well as the original Axios error in error.internalError. Handle this error as explained in the Axios documentation.

Example error object:

{
  apiModel: 'hints',
  apiModule: 'Hints',
  endpoint: 'http://localhost:8984/hints/hints/1/',
  logEndpoints: true,
  logInstance: true,
  action: 'get',
  created: 1603192001672,
  id: 'Hints_hints_1',
  params: {},
  status: 'failed',
  cancel: [Function: bound ],
  completed: 1603192001675,
  response: undefined,
  internalError:
  {
    isAxiosError: true,
    config: {
      url: 'http://localhost:8984/hints/hints/1/',
      method: 'get',
      params: {},
      headers: [Object],
      transformRequest: [Array],
      transformResponse: [Array],
      timeout: 0,
      xsrfCookieName: 'XSRF-TOKEN',
      xsrfHeaderName: 'X-XSRF-TOKEN',
      maxContentLength: -1,
      validateStatus: [Function: validateStatus],
      data: undefined
    },
    response: {
      status: 403,
      data: undefined,
      headers: undefined,
      config: [Object],
      request: [Object]
    }
  }
}

Example from VRR Test:

try {
  await seenHintsResource.get(null, id);
} catch (error) {
  expect(error.internalError.response.status).toBe(403);
}

Setup a Resource Object

You need to supply to VRR information on how to mount the endpoints and get data from the server. For that we need apiModule and apiModel. That information is used to organise the Vuex module store and keep track of things.

The resource object:

A resource object looks like

// Patients/resourceObject.js

export default {
  __name: 'Patients',
  Patient: {
    apiModel: 'patient', // model or endpoint name
    apiModule: '', // can be an empty string
    handler: {}, // optional, to manipulate data before it goes in the Store.
  },
};

Read more about handlers

Setting up a Resource

// Patients/resource.js

import vrrApi from 'plugins/vrr'; // The VRR object in the file you used to set up the VRR config
import resource from './resourceObject'; // Your module resource, do this for every model

// Use this method to create a Resource.  It takes in 1 parameter, resource which needs to be a Resource object in the above format.
const moduleResource = vrrApi.registerResource(resource);

export default moduleResource;

TODO: Import all resources and register each of them in a loop. Possibly with a JSON file from the API

Using a Resource

Whenever you want to use this endpoint, you will import the above file.

import PatientsResource from 'Patients/resource';

const {Patient: PatientModel} = PatientsResource;

Now we can use the PatientModel to make get, list, ... or use any of the helper methods, to manipulate data in the store and the server.

Glossary

apiModule

This will be the name of the store Module, which is used to namespace everything and should be in a folder with the same name.

The resource file is used to generate the state, mutators etc.
To use the store you still need to generate the resource file into a store module.

apiModel

This is the name of an object in your module. e.g. the posts of a user.

Methods

Once you have created a Resource, you can use it with the following methods

get

Resource.get(vueComponentInstance, id)

Param 1: Object instance, the pointer to the Vue instance consuming the API
Param 2: Number, the id of the model you want to get Param 3: Function, A callback passing you (data, store). When using this the Store is not getting updated automatically anymore.

The .get method fetches a specific object from the server and puts the resource data with id in the store.

Create

Resource.create(vueComponentInstance, obj)

Param 1: Object instance, the pointer to the Vue instance consuming the API Param 2: Object, the object/model you want to save (without id). Param 3: Function, A callback passing you (data, store). When using this the Store is not getting updated automatically anymore.

The .create method updates the server with new data and puts the resource data with id in the store.

Update

Resource.update(vueComponentInstance, id, obj)

Param 1: Object instance, the pointer to the Vue instance consuming the API Param 2: Number, the id of the model you want to update. Param 3: Object, the object/model you want to update. Param 4: Function, A callback passing you (data, store). When using this the Store is not getting updated automatically anymore.

The .update method updates the server with new data and puts the resource data with id in the store.

List

Resource.List(vueComponentInstance)

Param 1: Object instance, the pointer to the Vue instance consuming the API

The .list method fetches a collection of data from the server and puts the resource data with id in the store.

You can send query parameters as a second argument.
Resource.list(this, {ordering: '-name'});

asyncResourceGetter (computed property)

This method is similar to Vuex's mapGetters.

The idea is to get resources from the server whose you have the id for, reactively. If you have a id of a model/object and that id changes, the VRR will first check the store for it and if it doesn't find it it will get it from the server for you and update the store. So you can use the computed property this function returns as the pointer to the value you want.

In other words:

Loads in the specific object in the store. Use this to bind a state to a computed property. If the Object is not found in the store, it fills the store with data from the server.

Param 1: String, name of the computed property the Vue instance will receive
Param 2: Object, the resource Object
Param 3: String|Number, the computed property, or prop, with/or the id of the object you want or the name of the instance value/property to observe. Param 4: Function (optional), callback to transform the data from the store before providing it as the value of the computed property. If you don't need it just pass (data) => data. e.g.

computed: {
  ...asyncResourceGetter('currentUser', UserResource, 'this.myUserIdProp')
},

Get a nested object

Param 1: String, name of the computed property the Vue instance will receive
Param 2: Array, Array of the resource Objects, executed in order
Param 3: String|Number, the computed property, or prop, with/or the id of the first object.
Param 4: Array:Function, Array of functions, executed in order and with the data received from the previous function. If you don't need it just pass (data) => data. e.g.

computed: {
  ...asyncResourceGetter('currentUserCity', [UserResource, CityResource], userId, [(userData) => userData.cityId, (cityData) => data])
},

resourceListGetter (computed property)

Use this to bind a state to a computed property, but only get the data that matches the array passed in.

Will always fill in the store with server data when the object is not found.

Param 1: String, name of the local state
Param 2: Resource, pass in the resource Object
Param 3: String, the computed property name that has an array with IDs or a object to be used as a filter for the query

e.g.

computed: {
  ...resourceListGetter('userPosts', Posts, 'user.posts'),
},

activeRequests

Every time a request is made, we track this and also register the Vue component that asked for it.

Now we can group requests to the same endpoint and only fire one, while firing a done response for each of them.

We can also use the activeRequests as a computed property to check if VRR is done fetching everything, and to show a loading status.

const {activeRequests} = vrr;

computed: {
    ...activeRequests('activeRequests'),
}

Internals

The Resource Object

With that information VRR will create the store mutations method names, that follow the pattern:

 const mutation = [apiModule, `${action}${capitalizeFirst(apiModel)}`]
    .filter(Boolean)
    .join('/');

so, the module name can be an empty string for simple endpoints with only one level depth.

The whole resource object could be:

const MODULE = 'Patients';

export default {
  __name: MODULE,
  Patient: {
    apiModel: 'patient',
    apiModule: MODULE,
    handler: {}, // custom handlers (optional)
  },
  PatientFinderById: {
    apiModel: 'patientfinderbyid',
    apiModule: MODULE,
    handler: {
      list: (response) => response.data, // optional, in case you need custom handlers
    },
  },
};

As named before:

  • the apiModule is the name of the store module.
  • the apiModel is the name of the object in this module.
  • the handler object is where you can pass functions to change the data just before it is added to the store.

The apiModule and apiModel will be used to form the server endpoint, together with the baseUrl from the options (if given) and with the id (if given). The endpoint where VRR will look for data is:

endpoint = `${[this.baseUrl, this.apiModule, this.apiModel]
  .filter(Boolean)
  .join('/')
  .toLowerCase()}/`;

Contributors



piroskadar
commits

jens.alm
commits

perjor
commits

DevDependencies


Categories: Vue js