DEV Community

loading...
Cover image for How To Structure a Massive Vuex Store for a Production App

How To Structure a Massive Vuex Store for a Production App

Domagoj Vidovic
Frontend Engineer in a London tech-startup. Let's make our Frontend lives more magical ✨ JS/CSS/HTML enthusiast
・4 min read

When looking at Vuex tutorials, you can see most of them are quite simple.

The logic is explained well, but scalability suffers. How will this work in my production app?

Here’s a simple store example from Vuex official docs:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  }
})
Enter fullscreen mode Exit fullscreen mode
A simple store

There’s no need to explain this. I assume that you already have some Vue and Vuex knowledge prior to this article.

My goal is not to explain what a store, state, or mutations are.

Instead, I want to show you a massive store with 1,000+ state attributes, mutations, actions, and getters.

I want to teach you how to structure the store for the best maintainability, readability, and reusability.

It can have 100,000+ attributes. It would still be clear.

Let’s dive in.

Meet Modules

As we already said, keeping everything in one file will create a mess. You don’t want a 50,000+ LOC file. It’s the same as keeping your app in one component.

Vuex helps us here by dividing the store into modules.

For the purpose of this example, I will create a store with two modules. Note that the process is the same for 100+ modules, as well as 100+ actions, getters, and mutations within every module.


const userModule = {
  namespaced: true,
  state: () => ({}),
  mutations: {},
  actions: {},
  getters: {}
}

const organisationModule = {
  namespaced: true,
  state: () => ({}),
  mutations: {},
  actions: {},
}

const store = new VueX.Store({
  modules: {
    user: userModule,
    organisation: organisationModule
  }
})

store.state.user // -> `userModule`'s state
store.state.organisation // -> `organisationModule`'s state
Enter fullscreen mode Exit fullscreen mode
Updated store

The namespaced attribute is incredibly important here. Without it, actions, mutations, and getters would still be registered at the global namespace.

With the namespaced attribute set to true, we divide actions, mutations, and getters into the modules as well.

This is really helpful if you have two actions with the same name. Having them in a global namespace would create clashes.

const userModule = {
  namespaced: true,
  state: () => ({}),
  mutations: {},
  actions: {
    'SET_USER'() {},
    'SET_USER_LOCATION'() {}
  },
  getters: {}
}

store.state.user['SET_USER']() // correct ✅

stote.state['SET_USER']() // wrong ❌
Enter fullscreen mode Exit fullscreen mode
Namespaced module

As you can see, the module is completely “local” right now. We can access it only through the user object on the state.

Exactly what we want for our massive application.


Cool, now we have a store divided into modules!

However, I don’t like the hardcoded strings for actions. It’s definitely not maintainable. Let’s address this issue.

Types To Save You From Headaches

We don’t just want to access every property from every module in every file. That sentence sounds like hell.

We want to import them first. Then use mapGetters, mapActions, or mapMutations to achieve that.

// userModule.js
export const SET_USER = 'SET_USER'
export const SET_USER_LOCATION = 'SET_USER_LOCATION'

const userModule = {
  namespaced: true,
  state: () => ({}),
  mutations: {},
  actions: {
    [SET_USER]() {},
    [SET_USER_LOCATION]() {}
  },
  getters: {}
}

// vue file
import { mapActions } from 'vuex'
import { SET_USER, SET_USER_LOCATION } from './userModule.js'

...mapActions({
  setUser: SET_USER,
  setUserLocation: SET_USER_LOCATION
})
Enter fullscreen mode Exit fullscreen mode
Access actions with mapActions

This gives you a clear view of store attributes used by your Vue file.

But that’s not enough. Everything is still in one file. Let’s see what we can do to scale it properly.

Folder Structure

Ideally, we want to split modules into different folders. Within those modules, we want to split their mutations, actions, getters, state attributes, and types across different files.

The desired folder structure

The desired folder structure

Folder store will be created in the root folder of our project.

It will contain two things:

  1. index.js file
  2. modules folder

Before explaining the index.js file, let’s see how we divide a single module. Let’s check the user module.

All of its actions, mutations, and getters should be listed in the types.js file. So, something like:

// actions
export const SET_USER = 'SET_USER'
export const SET_USER_LOCATION = 'SET_USER_LOCATION'

// mutations

// getters
Enter fullscreen mode Exit fullscreen mode
store/modules/user/types.js

We’ll have a clear view by importing those consts every time we want to use them.

Let’s look at the actions now. We want to move them to the actions.js file.

To do so, we only need to copy the actions object within the module and export default it, while importing the types:

import { SET_USER, SET_USER_LOCATION } from './types.js'

export default {
  [SET_USER]() {},
  [SET_USER_LOCATION]() {}
}
Enter fullscreen mode Exit fullscreen mode
store/modules/user/actions.js

We will do the same thing for mutations and getters. The state attributes will remain in index.js (within the user module folder):

import actions from './actions.js'
import mutations from './mutations.js'
import getters from './getters.js'

const state = {}

export default {
  namespaced: true,
  state,
  actions,
  mutations,
  getters
}
Enter fullscreen mode Exit fullscreen mode
store/modules/user/index.js

Now we have all of our modules divided into multiple files.

The one thing remaining is to link all those modules in the index.js file within the store folder:

import Vue from 'vue'
import Vuex from 'vuex'

// Modules import
import UserModule from 'modules/user'
import OrganisationModule from 'modules/organisation'

Vue.use(Vuex)

const state = {}
const actions = ({})
const mutations = ({})
const getters = ({})

const modules = {
  user: userModule,
  organisation: organisationModule
}

export default new Vuex.Store({
  state,
  actions,
  mutations,
  getters,
  modules
})
Enter fullscreen mode Exit fullscreen mode
store/index.js

Conclusion

By using this architecture, we had zero problems with scalability in our massive production app.

Everything is so easy to find.

We know exactly where all the actions are triggered.

The system is highly maintainable.

If you have any recommendations for the improvements, please let me know. I would love to hear your opinion.

Discussion (21)

Collapse
elidrissidev profile image
Mohamed ELIDRISSI

I like this, we need more posts like this in DEV. Thank you for sharing!

Collapse
domagojvidovic profile image
Domagoj Vidovic Author

Glad to hear that! More content is coming 🙂

Collapse
jamesthomson profile image
James Thomson

Are you sure we don't need more Dark Mode posts instead?? 😂

But in all seriousness, this is exactly what DEV needs. Solid post @domagojvidovic 👏

Collapse
elidrissidev profile image
Mohamed ELIDRISSI

Ahh dark mode, more like "dark more!"

Collapse
cyberstrike profile image
Chris Scott

Built a large application with 4 modules, after 2 years of working on it I an say in full confidence that if you need modules you might as well just build a seperate application.

In no way did it create any advantages to have it all as one application. Even for shared components ad we eventually moved to a shared design system.

Collapse
domagojvidovic profile image
Domagoj Vidovic Author

But building a new application sometimes isn't an option...

Collapse
seangwright profile image
Sean G. Wright

How does mapActions know about the namespacing? Wouldn't identically named actions/mutations still cause problems when called from a component?

Collapse
neokms profile image
neokms • Edited

Easy

computed: {
            ...mapGetters('sites', {
                site: 'getSiteData',
            }),
            ...mapGetters('publications', {
                dataset: 'getPublicationList',
            }),
        }
Enter fullscreen mode Exit fullscreen mode
Collapse
seangwright profile image
Sean G. Wright

Awesome - I use createNamespacedHelpers but I guess I never realized that worked the same way as using mapActions directly with a namespace as the first parameter!

I like having strong types for my actions/mutations without having to import something, so I create an action function that uses JS Doc type unions

export const actionTypes = {
  UPDATE_DATA: 'UPDATE_DATA',
  LOAD_DATA: 'LOAD_DATA'
};

/**
 * @param {keyof actionTypes} actionType
 */
export function action(actionType) {
  return `dataModule/${actionType}`;
}
Enter fullscreen mode Exit fullscreen mode

In my SFCs I can use it like this:

import { action as dataAction } from './store'

export default {
  methods: {
    onSubmit() {
      this.$store.dispatch(dataAction('UPDATE_DATA'), { someData: [ ... ] });
    }   
  }
}
Enter fullscreen mode Exit fullscreen mode

The value I can pass to dataAction is strongly typed to be only one of the keys of actionTypes.

Collapse
king11 profile image
Lakshya Singh

Never used Vuex Modules before but NuxtJS provides a way through subdirectories I believe, I wonder if it uses this under the hood

Collapse
morficus profile image
Maurice Williams

This is exactly what Nuxt is doing under the hood.

Collapse
domagojvidovic profile image
Domagoj Vidovic Author

Hmm, I'm not sure that it's connected

Collapse
snuppedeluxe profile image
SnuppeDeluxe

I tried to make it in a small tutorial project on my own.
I got an error: "[nuxt] store/index.js should export a method that returns a Vuex instance. "
Maybe I'm a bit to stupid for it, but in /store/index.js is a function called "export default new Vuex.Store({...})
That's the exported method to return a Vuex instance...

That's so difficult for now :-(

Collapse
davey profile image
Davey

This is fantastic. Thanks :)

I didn't know about this and have been putting everything in one file. I'll definitely be using this technique in the future.

Collapse
domagojvidovic profile image
Domagoj Vidovic Author

Glad you like it! Start as soon as possible :) let's tackle that tech debt!

Collapse
devanks profile image
Devan Sambasivan

Love this. It would have helped me greatly when we were setting up our apps.

Collapse
domagojvidovic profile image
Domagoj Vidovic Author

Thanks a lot mate! We need more tutorials/guides about scaling a huge system, not just the basics!

Collapse
terabytetiger profile image
Tyler V. (he/him)

The first Vue Apps I built at work I didn't know about modules and the stores definitely got a bit out of hand 😅

Collapse
domagojvidovic profile image
Domagoj Vidovic Author

I can imagine that chaos 😆

Collapse
snuppedeluxe profile image
SnuppeDeluxe

I'm still learning js and vue. I love posts like this to learn best practice from the beginning.

Collapse
domagojvidovic profile image
Domagoj Vidovic Author

Yep, it's good to create as low tech debt as possible!