Git Product home page Git Product logo

vuex-rest-api's Introduction

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.

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)

Minimal example

Yep, here: https://codesandbox.io/s/8l0m8qk0q0

API and further documentation

The docs are now available under http://vuex-rest-api.org

vuex-rest-api's People

Contributors

bjoernklose avatar blainehansen avatar christianmalek avatar cmllr avatar dboune avatar dries007 avatar glandos avatar ilyaryabchinski avatar jgerhold avatar johndab avatar m-demydiuk avatar markhaehnel avatar rmaclean-ee avatar slurmulon avatar tobino avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

vuex-rest-api's Issues

v3

This document isn't final. I will update it continuously.

Feature ideas:

  • custom pre-flight validation methods (see #54 (comment))
    • integration with a JSON Schema validator e.g. AJV
  • make endpoints mappable via object/config file (see #54 (comment))
  • allow easy customization of store object, that you don't have to use Object.assign() anymore after creating the store object
  • check usage of namespaces
  • add alternative way of adding actions (see #59)
  • add option to disable error throwing (is this really helpful?)
  • add global onError interceptor that can be overridden at resource entity level (see #54 (comment))
  • add state.loaded.<propertyName> to know if state was loaded at least once
  • add Axios.requestConfig.transformRequest method to the resource action options (see #77)
  • add explicit queryParams (see #44)
  • use option parameter instead of multiple parameters (see #84 (comment))
  • support Fetch API (see #93)
  • make requests cancel each other by demand (see #54 (comment))
  • make error and pending properties optional (see #95)
  • add json:api support

Mandatory changes:

  • add tests 🐛
  • improve readme
    • split readme into docs and simple readme file
    • add examples
  • make baseUrl changeable after creating store (just set baseUrl of axios instance)
  • make default error state configurable
  • add option to don't change state if error happens
  • make getStore({ createStateFn: true }) the default
  • add logo
  • implement caching (see #79)
  • implement the possibility to cancel requests (see #79)
  • implement the possibilty to access params in onSuccess, onError fn (see #79)
  • make requests cancellable (see #54 (comment))

Strange case of double-promise in production

While developing locally, this.$store.dispatch('...') returns a promise. When the code is in production, it returns a promise that returns a promise (I need to use await await this.$store.dispatch('...')). Any idea why that is?

Id Parameter is always undefined

I have this action definition

  .get({
    action: 'getUser',
    property: 'user',
    path: ({ id }) => {
      console.log('The Arg is ', id)
      return `/tenants/${id}`
    }
  })

And I am dispatching like this:

Try 1:

js
created: function () {
        this.$store.dispatch({
        type: 'getUser',
        data: { id: this.$route.params.id }
      })

Try 2:

js
created: function () {
      this.$store.dispatch('getUser', {
        data: {
          id: 1
        }
      })

Try 3:

js
created: function () {
      this.$store.dispatch('getUser', { id: 1243 }, null )

In all cases the id is undefined. IMHO it should have an id.

Alter body or header dynamically

Hello, is there a way to change the body or the header of the request when it gets called such as the pathFn ?

A use case:
I dispatch a "connexion" action:

this.$store.dispatch('connexion', {
  data: {
    _username: ...,
    _password: ...
  }
}

In this example, my component emitting the action is holding the request's body. If I change it here, I break the api call. If I have to transform it into a FormData() object, I have to do it in place into the component.

Is there a way to avoid this ? Can I do it in the Vapi store definition ?

By the way, thanks for this cool library !

Cheers.

Request/Suggestion - path parameters vs query parameters

I believe it would be helpful if there were either a way to filter the params prior to passing them to axios but after using them in the path without using paramsSerializer, or if they were just considered two separate things entirely. I would prefer the latter.

Unless I've missed something, without importing a serializer like qs and using it in paramsSerializer it does not seem possible to strip params that have been used in the path.

The following results in a URL like:
/projects/{projectId}/participants?projectId={projectId}&active=1

.get({
  action: 'getProjectParticipants',
  property: 'participants',
  queryParams: true,
  path: { projectId } => `/projects/${projectId}/participants`
})
getProjectParticipants({
  params: {
    projectId: '123',
    active: '1'
  }
})

Something like the following would feel more intuitive to me. (I'm not intending to suggest key names)

.get({
  action: 'getProjectParticipants',
  property: 'participants',
  path: { projectId } => `/projects/${projectId}/participants`
})
getProjectParticipants({
  path: {
    projectId: '123',
  },
  query: {
    active: '1'
  }
})

Axios errors

Hi,

Related #34 and #35 The reason I'm asking is that the error object only shows:

state.error.: Error

No actual detail there, whereas on the console log I see:

Unhandled promise rejection
Error: Request failed with status code 404
Stack trace:
createError@webpack-internal:///./node_modules/axios/lib/core/createError.js:16:15
settle@webpack-internal:///./node_modules/axios/lib/core/settle.js:18:12
handleLoad@webpack-internal:///./node_modules/axios/lib/adapters/xhr.js:77:7

Thanks.

POST

Hi,
please, how can i put payload to the POST action?

I have this action in Resource object:
.addAction({ action: 'createChildren' method: 'post', property: 'children', pathFn: ({ parentId }) => '/parents/${parentId}/childrens' })

From component i call this:

createChildren({parentId:'c539fba9-5ee1-3670-b320-11fb967ce11c'}, {name:'Pedro'})

HTTP request has been ok (with correct URI), but payload has been empty.

Axios request config params object

Hi,
how can i use Axios request config params object for Resource object actions?
Action of Resource object has function pathFn for adding some parameters to the URL, but i need add some handy parameters with stringifying needing and Axios has paramsSerializer function for that (usable with Qs library). Please, how can i use Axios paramsSerializer function with your helper?

Resource object wrapper with configured Axios instance and paramsSerializer option:

import Qs from 'qs'
import Axios from 'axios'
import { Resource } from 'vuex-rest-api'

export default class ResourceWrapper {
    constructor(baseURL = null, options = {}) {
        baseURL = baseURL || 'http://localhost:8003/api/admin'
        options.axios = Axios.create({
            paramsSerializer: function(params) {
                return Qs.stringify(params, {arrayFormat: 'indices'})
            }
        })

        return new Resource(baseURL, options)
    }
}

silent errors

Using your module since 2 weeks I see in my projects that mutationFailureFn never got called even in case of failure.
Instead, module will call mutationSuccessFn with an undefined as payload which causing me a lot of headaches trying to figure this out.
Putting some .catch()or error callback to my .then() don't work (I tried in many places).
I'm currently preparing you a fiddle demonstrating this behavior.

How we can change Axios headers

Hi Chris,
please, how we can manage Axios headers at created Vapi instance?
For example, if we need add Authorization header for API after successfully login of user or so on...
Thank you.

Usage problem

Hi,

I have a problem using your library. Could we please get in touch.

Best regards
Petr

New instance (reuse) of module

Hi,
please, how i can make a new instance of module?
So, i need use same module in multiple namespaces, but with different state of that module.
Thank you.

Dispatching POST request doesn't send the second argument

I've got a super simple express.js server up and running with body-parser. I can successfully send a request and receive it on the server, but when I log the body of the request it's empty. I tried to make a request with postman and was able to get the object on the server, but I had to set content-type to x-www-form-urlencoded. I went back to my code and created a new Axios instance with content-type set to form-urlencoded, but I still could not see the body of the request on the server.

Here's my store:

import Vue from 'vue'
import Vuex from 'vuex'
import Vapi from 'vuex-rest-api'
import axios from 'axios'

Vue.use(Vuex);

var ax = axios.create({
    baseURL: "http://localhost:3000",
    timeout: 1000,
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});

const todos = new Vapi({
    axios: ax,
    state: {
        todo: null,
        todos: []
    }
})
.get({
    action: "getTodos",
    property: "todos",
    path: "/api/todos"
})
//This one won't send the payload I define in another component.
.post({
    action: "addTodo",
    property: "todos",
    path: "/api/todos",
    onSuccess: (state, payload) => {
        console.log(payload);
    }
})
.getStore();

export const store = new Vuex.Store(todos);

Here's the component where I dispatch it.

<template>
    <div id="app">
        <h1>{{ message }}</h1>
        <router-link to="/admin">Go to the admin page!</router-link>
        <ul>
            <li v-for="(item, index) in this.$store.state.todos">
                <template>
                    <div class="input-group">
                        <input type="checkbox" :id="index" v-model="item.checked" tabindex="0">
                        <label :for="index" :class="{strikethrough: (isStrikeThrough && item.checked), zebra: (isZebraStripes && index % 2 === 0)}">{{ item.description }}</label>
                    </div>
                </template>
            </li>
        </ul>
        <input v-model="tempMessage">
        <button @click="addTodo">Submit</button>
    </div>
</template>
<script>
export default {
    name: 'home',
    data() {
        return {
            message: "Todo App",
            tempMessage: ''
        };
    },
    computed: {
        isStrikeThrough() {
            return this.$store.getters.isStrikeThrough;
        },
        isZebraStripes() {
            return this.$store.getters.isZebraStripes;
        }
    },
    methods: {
        addTodo() {
            var tempObject = { description: '', checked: false };
            if (this.$store.getters.isSaltyDeveloperMode) {
                var saltyPhrases = this.$store.getters.getSaltyPhrases;
                var saltyIndex = Math.floor(Math.random() * saltyPhrases.length);
                tempObject.description = saltyPhrases[saltyIndex];
            }
            else {
                tempObject.description = this.tempMessage;
            }
            var body = {
                tempObject: tempObject
            }
            //My body object will not log to the server.
            this.$store.dispatch('addTodo', body);
            this.tempMessage = '';
        }
    },
    mounted() {
        //Initial loading of todos works just fine!
        this.$store.dispatch('getTodos');
    }
}
</script>
<style>
.strikethrough {
    text-decoration: line-through;
}

.zebra {
    color: white;
    background-color: black;
}
</style>

And my simple express js server.

var express = require('express'),
	bodyParser = require("body-parser");
	path = require('path');
	app = express(),
	PORT_NUMBER = 3000;

var todos = [ 
			{ description: "Example todo!", done: false },
            { description: "Another Example todo!", done: true }
        ];

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

app.use('/dist', express.static('dist'));

app.get('/api/todos', function (req, res) {
	res.send(todos);
});

app.get('/api/todos/:todo', function (req, res) {
	res.send(todos[req.params.todo - 1]);
});

app.post('/api/todos', function (req, res) {
	console.log("We got a request!");
	console.log(req.body);
});

app.put('/api/todos/:todo', function (req, res) {

});

app.delete('/api/todos/:todo', function (req, res) {
	var removedItem = todos.splice((req.params.todo - 1), 1);
	res.send(removedItem);
});

app.get('*', function(req, res) {
	res.sendFile(path.resolve('index.html'));
});

console.log("About to listen on port " + PORT_NUMBER);

app.listen(PORT_NUMBER);

Here's what I expect when I log the body to the console: { body: { tempObject: {description: "Something from the text input", checked: false} } }

Here's what I get: {}

I think I'm just misunderstanding how the POST requests are done, but I can't figure it out. Is body parser my problem? Should I be looking for a different property in the request object?

Serialize parameters to get something easy

How can I pass some params like this without having to show each one of it, because this API works with all params and also with only one.

This is the API:

GET api/Postagem?CodigoUsuario={CodigoUsuario}&TipoLeitura={TipoLeitura}&Titulo={Titulo}&RowCount={RowCount}&Page={Page}

GET api/Postagem?CodigoPostagem={CodigoPostagem}

These are the codes:

this.getNotifications({
    CodigoPostagem: email.CodigoPostagem,
}).then((res) => {

second one:

this.getNotifications({
        params: {
          CodigoUsuario: this.userId,
          TipoLeitura: this.filterGeneral,
          RowCount: this.rowCount,
          Titulo: this.filter,
          Page: Math.floor((this.emails.length / this.rowCount) + 1),
        },
}).then((res) => {

Passing them to it, making it dynamic:

const HOST = process.env.NODE_ENV === 'production' ? '********' : 'http://localhost:8088';

const resource = new Resource(HOST)
  .addAction({
    action: 'getNotifications',
    method: 'get',
    property: 'notifications',
    pathFn: ({ params }) => `/api/Postagem/${params}`,
  });

const notifications = createStore(resource);

export default new Vuex.Store({
  modules,
  ...notifications,
  strict: process.env.NODE_ENV !== 'production',
});

By the way, awesome package.
Thank you!

how to extract modules to separate files

Hi there, I'm using the latest version and it's great and it's working fine if I set everything up from inside the store.js file

but I would like to abstract my resources to separate files, or at the very least to one file called Apis.js which is inside ./modules/Apis.js

Clearly I'm just brain-farting here but I don't know how to make this work. This is what I've got so far

store.js:

import { topics, nav } from './modules/Apis';

const store = new Vuex.Store({
  modules: {
    topics,
    nav,
  },
});

Apis.js:

import Vapi from 'vuex-rest-api';

const topics = new Vapi({
  baseURL: '/api',
  state: {
    topics: [],
  },
})
  .get({
    action: 'getTopic',
    property: 'topic',
    path: ({ id }) => `/topics/${id}`,
  })
  .get({
    action: 'getTopics',
    property: 'topics',
    path: ({ limit }) => `/topics?limit=${limit}`,
  })
  .post({
    action: 'updateTopic',
    property: 'topic',
    path: ({ id }) => `/topics/${id}`,
  })
  .getStore();

const nav = new Vapi({
  baseURL: '/api',
  state: {
    nav: [],
  },
})
  .get({
    action: 'getNav',
    property: 'nav',
    path: ({ season }) => `/nav?season=${season}`,
  })
  .getStore();

export default {
  topics,
  nav,
};

And then in my components I want to call (for example the nav) like this:

export default {
  computed: {
    ...mapState({
      nav: state => state.nav.nav,
      pending: state => state.nav.pending,
      error: state => state.nav.error,
    }),
  },
  methods: {
    ...mapActions(['getNav']),
  },
  created() {
    this.getNav({ params: { season: this.currentSeason } });
  },
}

Typescript unable to locate declaration file

  • Are you using the newest version of vuex-rest-api? YES: 2.2.1
  • Did you read the README? YES
  • Did you check the existing issues? Maybe there's already a solution to your problem. YES

Using typescript, though the module has .d.ts files, I receive the error:

TS7016: Could not find a declaration file for module 'vuex-rest-api'

Adding a single line to package.js providing the path to index.d.ts seems to resolve the issue.

  "types": "dist/index.d.ts"

Returned promise from HTTP PUT method catch error on success response (v2)

Hi,
i have problem with returned promise (probably from Axios?) on success PUT request with HTTP status code 200. Promise catching error, but i don't know why. If i try pure Axios with PUT method, then returned promise is OK. Before some days i update packages of my project (latest vuex-rest-api and required Axios version), but i don't know, if is this a problem.

Improvement ideas

I'm willing to improve Vapi. Any ideas?

Things I'm planning to do are:

  • create tests
  • simplify the API, make it more intuitive (ideas how?)
  • (maybe) add request and response interceptors so you are able to transform data or change axios config etc.

I would love to hear ideas from everyone who is interested in this project. :)

Uncaught TypeError: Cannot match against 'undefined' or 'null'.

Hi,

I try to create an API module which will be used by other modules in my store to ask for data then apply my business logic on top of it.
Here is my api.js file:

import Vue from 'vue'
import { createStore, Resource } from 'vuex-rest-api'

const options = {
  axios: Vue.axios
}
const resource = new Resource(__API_ROOT__, options)

// Auth (User and Guest cases)
resource
  .addAction({
    action: "login",
    method: "post",
    property: "token",
    pathFn: () => '/auth/login'
  })
  .addAction({
    action: "getGuestHash",
    method: "get",
    property: "guest_hash",
    pathFn: () => '/auth/get_guest_hash'
  })
// Cart
  .addAction({
    action: "getCart",
    method: "get",
    property: "cart",
    pathFn: (id) => `/carts/${id}`
  })
  .addAction({
    action: "createCart",
    method: "post",
    property: "cart",
    pathFn: () => `/carts`
  })
  .addAction({
    action: "cleanCart",
    method: "put",
    property: "cart",
    pathFn: () => `/carts/clean`
  })
  .addAction({
    action: "getActiveCart",
    method: "get",
    property: "activeCart",
    pathFn: () => `/carts/active`
  })
  .addAction({
    action: "changeDomiciliationOffer",
    method: "put",
    property: "cart",
    pathFn: (id) => `/carts/${id}`
  })
  .addAction({
    action: "addOffer",
    property: "cart",

  })

export default createStore(resource)

my store/index.js file:

import Vue from 'vue'
import Vuex from 'vuex'
import mutations from './mutations'
import actions from './actions'
import getters from './getters'
import api from './modules/api'

Vue.use(Vuex)

const state = {
  count: 0
}

const store = new Vuex.Store({
  state,
  mutations,
  actions,
  getters,
  modules: {
    api
  }
})

export default store

Here is the error I get from the console:
capture d ecran 2017-05-10 a 15 13 03

Improve catch error to return response

When using axios itself, you can use error.response to get the response of an error, e.g.:

axios.post('/formulas/create', {
	name: "",
	parts: ""
})
.then(response => { 
	console.log(response)
})
.catch(error => {
    console.log(error.response)
});

But right now it only return the scope of the error, e.g.:

[error] Error: Network Error
    at createError (D:\xampp\htdocs\xxxx\node_modules\axios\lib\core\createError.js:16:15)
    at XMLHttpRequest.handleError (D:\xampp\htdocs\xxxx\node_modules\axios\lib\adapters\xhr.js:87:14)

Is there a way to change that or I'm doing something wrong?

Thanks!

Getters are ignored

We are using vuex-rest-api 2.4.4 and I created a use case with modules that is working except getters. When I add the getters section they aren't added to the parent store.
According to the docs add-additional-state-actions-and-mutations-to-the-store this should be possible as its just regular store object.

import Vuex from 'vuex'
import Vue from 'vue'
import Vapi from 'vuex-rest-api'

Vue.use(Vuex)

const tenants = new Vapi({
  baseURL: '/api',
  state: {
    tenants: [],
    counter: 0
  },
  getters: {
    getTenantById: state => {
      return 'here should be the tenant by id'
    }
  }
})
  .get({
    action: 'mainTenants',
    property: 'tenants',
    path: '/tenants'
  })
  .getStore()

export default tenants

I also made a simple example and this one works just fine

const example = {
  getters: {
    moo: state => {
      return 'you'
    },
    getTenantByfoo: (state) => (id) => {
      console.log(state)
      console.log(id)
      // return state.tenants.find(t => t.id === id)
      return 'getTenantById'
    }
  }
}

Main Store

const store = new Vuex.Store({
  strict: true,  // Do not enable strict mode when deploying for production! process.env.NODE_ENV !== 'production',
  getters,
  modules: {
    menu,
    app,
    tenants,
    example
  },
  state: {}
  mutations: {}
})

Should I add the getter separately?

How to change a mutation

I'm trying to change a mutation that createStore has done, I found in the docs that this could execute someting when it success:

  // This function will be called after successfully resolving the action.
  // If you define this property, only the corresponding
  // pending annmd error properties will be set, but not state[property].
  // The method signature is (state, error).
  mutationSuccessFn: Function,

But my problem is that I need to concat this state with the old one, is that possible?

Thanks!

How to dynamically set baseURL

Some of my clients will receive some notifications from their servers, so I need to dynamically set the baseURL for each one of them, the only way I find it right now is passing them in the localStorage and consuming that before config are set.

Is there another way to dynamically change the baseURL?

Thanks!

Typescript: ResourceActionOptions lists method option as required

  • Are you using the newest version of vuex-rest-api? YES: 2.2.1
  • Did you read the README? YES
  • Did you check the existing issues? Maybe there's already a solution to your problem. YES

In this case the method property is set by the get method of Vapi, so method is not required. Additionally Resource.add() defaults the method property to get.

items.get({
  action: 'listItems',
  property: 'items',
  path: '/'
})

TS2345: Argument of type '{ action: string; property: string; path: string; }' is not assignable to parameter of type 'ResourceActionOptions'.
Property 'method' is missing in type '{ action: string; property: string; path: string; }'.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.