Vuex is the official application state management library designed specifically for the Vue.js framework.

Vuex implements a state management pattern that serves as a centralized data store for all components of an application.

As the application grows, this storage grows and the application data is placed in one large object.

To solve this problem, Vuex divides the storage into modules. Each such module can contain its own state, mutations, actions, getters, and built-in submodules.

Working on the CloudBlue Connect project and creating another module, we caught ourselves thinking that we were writing the same boilerplate code over and over again, changing only the endpoint:

  • A repository that contains the logic for interacting with the backend;
  • A module for Vuex that works with the repository;
  • Unit tests for repositories and modules.

In addition, we display data in a single list or table view with the same sorting and filtering mechanisms. And we use almost the same data extraction logic, but with different endpoints.

In our project, we love to invest in writing reusable code. In addition to the fact that this reduces development time in the long term, it reduces the number of possible bugs in the code.

To do this, we created a factory of standard Vuex modules, which reduced the writing of new code to interact with the backend and storage (store) almost to zero.

Creating a Vuex module factory

  1. Base repository

The BaseRepository unifies the work with the backend via the REST API. Since these are normal CRUD operations, I will not dwell on the implementation in detail, but will only focus on a couple of main points.

When creating an instance of a class, you need to specify the name of the resource and, optionally, the version of the API.
On the basis of them, an endpoint will be formed, which we will refer to further (for example: / v1 / users).

There are also two helper methods worth noting:

  • query – just responsible for executing queries.
  • getTotal – gets the total number of items.
    In this case, we are making a request to get the collection and looking at the Content-Range header, which contains the total number of elements: Content-Range: – / .

The class also contains the main methods for working:

  • listAll – get the entire collection;
  • list – partially get a collection (with pagination);
  • get – get an object;
  • create – create an object;
  • update – update the object;
  • delete – delete an object.

All methods are simple: they send the appropriate request to the server and return the desired result.

I will separately explain the listAll method, which gets all the available elements. First, using the getTotal method described above, we get the number of available items. Then we load the elements in chunkSize batches and combine them into one collection.

Of course, the implementation of getting all elements may be different.

BaseRepository.js

To start working with our API, you just need to specify the name of the resource.
For example, get a specific user from the users resource:

What to do when additional actions need to be implemented?
For example, if you want to activate a user by sending a POST request to / v1 / users /: id / activate.
To do this, we will create additional methods, for example:

Now the API is very easy to work with:

Factory storage

Due to the fact that we have a unified behavior in the repository, the module structure will be similar.
This feature allows you to create standard modules. Such a module contains state, mutations, actions, and getters.

Mutations

In our example, one mutation will be enough, which updates the values ​​of objects.
As value, you can specify both the final value and pass the function:

State and getters

As a rule, the storage needs to store some kind of collection, or a specific element of the collection.
In the users example, this could be a list of users and detailed information about the user we want to edit.

Accordingly, at the moment, three elements are sufficient:

  • collection – collection;
  • current – the current item;
  • total – the total number of items.

Action games

Actions must be created in the module that will work with the methods defined in the repository: get, list, listAll, create, update and delete. By interacting with the backend, they will update the data in the repository.

If you wish, you can create methods that allow you to install data into the storage without interacting with the backend.

Warehouse factory

The store factory will serve up modules that need to be registered in the store using the registerModule method: store.registerModule (name, module) ;.

When creating a generic store, we pass the repository instance and additional data that will be mixed into the repository instance as parameters. For example, it could be a method that will activate a user.

StoreFactory.js

Usage example

To create a standard module, it is enough to create an instance of the repository and pass it as an argument:

However, as in the user activation example, the module must have a corresponding action.
Let’s pass it as a store extension:

Resource factory

It remains to put everything together into a single resource factory, which will first create the repository, then the module, and finally register it in the store:

ResourceFactory.js

Resource Factory Example

The use of standard modules is very simple. For example, creating a module for managing users (including a custom action to activate a user) is described by one object:

Inside the components, the use is standard, except for one thing: we set new names for actions and getters so that there are no collisions in the names:

If you want to get any nested collections, then you need to create a new module.
For example, working with purchases made by a user might look like this.

Description and registration of the module:

What can be improved

The resulting solution can be modified. The first thing that can be improved is the caching of results at the store level. The second is to add post-processors that will transform objects at the repository level. The third is to add support for mocks, so that you can develop the front-end until the back-end is ready.
If the continuation is interesting, then write about it in the comments – I will definitely write a continuation and tell you about how we solved these problems.

Summary

Writing your code in a DRY way will make it maintainable. This is also available thanks to the API design conventions in our team. For example, the method with determining the number of elements through the Content-Range header is not suitable for everyone, you may have another solution.

By creating a factory of such typical (generic) modules, we practically got rid of the need to write repetitive code and, as a result, reduced the time both for writing modules and writing unit tests. In addition, the code has become monotonous, and the number of random bugs has decreased.

I hope you enjoyed this solution. If you have any questions or suggestions, I will gladly answer in the comments.