Blog: JS

Restructuring my SPA and why I kept my CRUD calls out of Vuex

Published

I love working with Vue – and have used it numerous times for easily adding components to existing sites, including fetching data from APIs, and now am building a Single Page Application (SPA) too.

In my traditional server-rendered sites that had some Vue magic added, there were some complex forms that used an event bus to respond to different actions – but with Vue 3 around the corner, the concept of an event bus is being deprecated, and the recommendation is to look at Vuex instead.

In my SPA, I spent some time looking at Vuex – not just what it was and how it worked, but also looking at recommended practices, and what to actually store.

But before getting started with how to restructure my approach, I’ll just share how my app was structured.

My old (and not ideal) approach

Within my JS folder, I had organised folders for the store, routes, components, plugins, mixins – you name it, there was a folder. And a “views” folder that housed the actual views. So for CRUD that’s an “index” view for the table of all data, and an “edit” for creating and updating.

Old SPA file structure, distributed through my JS source

One irk I had – but wasn’t sure how to approach a change – was that it meant that as I added new views, code was scattered through these folders – some in store, some in routes, some in views. And knew that as the app grew, this would get harder. And thankfully, the review process that triggered this blog post has solved this, but more on that later.

Within the “index” or “edit” views, these needed data – so would use a simple api helper that I could import to perform get, put, post and delete requests without having to code axios logic every time.

An extract of the API helper for axios calls

 

But also hard-coded each API endpoint in each view.

Hard-coded API endpoints in multiple components

And it all works really well. But there’s a difference between working, and being well-structured and maintainable. Reflecting back, I can see how idiotic it is to store API endpoints in components that may need data to be used elsewhere.

Something had to change… but I was a bit overwhelmed by the options and considerations. Lots of reading, trialling and refactoring has led to some changes in my structure – and the end result has me really happy. So this is my story.

Getting started with Vuex

An article by Marcus Oberlehner was a great read that talked about what to store (and when and why), and that the “Let’s store everything in Vuex” approach isn’t always the right answer too. This resonated with me… why make something more complicated than it needed to be especially when it actually adds no value?

Getting my head around state, getters, actions and mutations – plus namespaced modules – was just the first part: next up, following what is recommended as best-practice. And that led me to API calls.

The concept of state management is great – centralised state that any part of my app can access when it needs – makes sense.

But the recommendation of using actions (and mutations when needed) for your API calls had me a little perplexed. If I were creating a basic CRUD app, and in my “edit” view, why should the API call be in the Vuex store, and store that data globally?

The first answer is easy: reusability. Makes sense: I’ve loaded data, and stored it client-side for faster re-use later. Great. But the more complicated query is how meaningful this actually is for the specific purpose of CRUD. Yes, data in the central store can be more easily reused without a server hit. But what if the data changes on the server by another user? How do we know when we need to refresh the cache? Poll the server? Well that is a server hit, so why not pull on demand anyway. Two-way communication with web sockets was one idea, but this then creates a greater level of complexity too. And for the purpose of the app, is it really worth it?

The second answer makes sense too: it keeps business logic away from the view. And I love this idea: separation of concerns – and in hindsight, my initial approach totally disregarded this. But I found the implementation with Vuex to be more overwhelming than it sounds on the surface. The idea of using Vuex for this sits fine with me – but the implementation for API calls in a CRUD use-case didn’t feel quite right.

Implementing API calls in Vuex

But I took what I had learnt on-board, and implemented my CRUD API calls in Vuex. This meant creating a module for Vuex with the necessary actions and mutations to get and store data from the API – and reworking the data source in the view component to pull data from the Vuex store (and push it back to Vuex too).

The code in the view component itself, while no longer including any API calls directly (huge win), was able to get the details from the store with no problems and read really well – but in order to use v-model on any form fields, you need to write explicit get and set methods for each computed property. For two properties, fine. But if a form has 20 properties, that seems like a lot of extra code. Pair this with the additional code and complexity around using Vuex to begin with, it felt like there was far more coding needed for little perceived benefit for the scope of CRUD-type calls.

How else could this be done? (or why is this best practice?)

Further reading and research found that I wasn’t alone: some interesting opinions are on the Vue forums but also a really thought-provoking article from M Wallace: and while Wallace goes on to demonstrate the API calls in Vuex, he also has some examples of different approaches to where your data comes from.

Looking at his chart, old me was doing down the Parallel Self-Contained approach: I would have a component, and it would call the data it needed when it needed it, from the API. Remember above, I had my generic api helper that handled the axios calls, and had my API endpoints in each individual CRUD component. 

And this is where I see things were not good. If I needed to use the same API endpoint in different components, I’d end up having to include those URLs in multiple locations. What happens if one needs to change?

This degree of hard coding and tight coupling is not good.

But then implementing the CRUD API calls in Vuex also introduces tight coupling – and forces any app that wanted to reuse code to require use Vuex too.

Defining state (and when to use Vuex)

For me, the distinction here is that Vuex is great for application state: what toasts are in the queue, is the app’s nav open or closed, what are the properties of the logged in user.

My CRUD views needs state – but state that is local to that component only. So why complicate it with Vuex actions, mutations, getters and explicit computed get and set methods?

In the end, I settled on a bit of a combination of Service + Store approach.

I still am using Vuex – but only for the application’s state – not for every component’s state for the sole reason of “just because”.

The source of truth here is the API endpoint, not the store on each client – when a table needs to be loaded, the API endpoints handle filtering and pagination. When an object needs to be edited, the latest data is pulled from the API as needed. It makes sense for a CRUD app to rely on the API to get data when it is needed – that way, the complications of needing to consider how to refreshing the store are removed.

Redefining structure

There’s so much to take on-board here – Vuex in itself, creating more reusable code, some loose coupling, how poor form it is to have API endpoints in individual components, files everywhere. Time to rethink this.

First of all, rather than API endpoints in actual components, I created a simple service for each endpoint that handled the API calls (and still used my api helper for less axios re-writing every time). This can then be tailored to the specific object, and what verbs are needed. Load, in this example, is performing a single preload of the endpoint, but the _getEndpoints function could return an object of multiple endpoints - so if I need a component to make multiple calls, I can group these together with axios for an easier implementation. Just clarifying why that's there.

An object-specific API helper

In removing API endpoints from the CRUD components, I’ve also reworked the structure to be a module-based file structure. I’ve now created a modules folder, where I can place all related files for a module, including the Vue components themselves, route definitions and API wrappers that contain the business logic around accessing data. Now if I need to call a specific object type, I have an ES6 importable wrapper for the API for that object – and now decoupled from the components.

A revised module-based file structure

This makes me very happy, and feel it will provide longevity to the project – easily create reusable modules (including routes, global search behaviour, API wrappers and router views) – but also not overuse the Vuex store for every API call, just because. And a happier developer is one who loves to code even more. Well, I’ve found it to be true at least.

I now have my table views, my edit views, my view views, all pulling and pushing data as needed through the API wrapper, and keeping track of their state locally given as soon as the route changes, the data is no longer needed – so why try to store it in Vuex? For the basic CRUD operations, this makes most sense to me, and doesn’t solely rely on Vuex – meaning that modules could be re-used in a non-Vuex project without masses of refactoring.

And if I did need a store for a module, I can add that to this structure too. It just helps grouping related code together - routes, API helpers, global search instructions and views. Less folder traversing and more organisation. Happy happy.

Don’t get me wrong: Vuex still has its place. But for me, the clear distinction I will be asking myself when using Vuex is this:

Does this data need to be shared among components in my app?

If the answer is yes, then Vuex is a great fit. But if the answer is no, simplifying the component and data access itself, to me, makes sense.

A great example - the logged in user’s details – like their name, email address and avatar. Storing this in Vuex is logical – data can be accessed without an API call each time– and accessible from any component in the app. It isn’t as time-sensitive, and most of the time, you’re the user updating your email or own avatar, so we can refresh the user on login, on instantiation (if restoring) or after saving. And yet still have it globally accessible with ease.

My biggest takeaway of getting started with Vuex is around that “let’s store everything in Vuex” phase that Oberlehner talked about: just because you can store everything in Vuex doesn’t mean you should. Or that you have to.

I don’t have a huge network of Vue developer friends – and would love to have an open discussion about how you work with Vuex in your projects (and if you put all of your API calls in Vuex, why). I’d love to have a chat – find out more about your processes, the “why” questions and a bit of a chat about thoughts on your approach to Vuex – drop me a line via the contact page on my site, or find me on Twitter @MartyFriedel.

Blog

View all
Music

Rise Like Smoke

This is one I was pondering over for a while - and is a bit varied but also has some superb vocals, amazingly produced tracks, and some beautiful instrument flavours...

Continue reading...

JS

Why I chose TinyMCE

This article was written for and originally appeared on Blueprint by Tiny. I have been using TinyMCE in projects for years. Many years. Over a decade in fact....

Continue reading...

Game

Why I like playing with myself

Who doesn't like playing with themselves? I guess it all depends on what you're hoping to get out of said playing. Oh, and by the way, I'm talking about video...

Continue reading...

CSS

Streamlining your CSS development

This article was written for and originally appeared on Blueprint by Tiny. I remember when I started writing CSS – and how incredibly mind-blowing it was to...

Continue reading...

I am the Development Director (and co-owner) at Mity Digital, a Melbourne-based digital agency specialising in responsive web design, custom web development and graphic design.
Mity Digital