Getting started

Vuex ORM Overview

The best thing to do is to avoid nested structures. Normalize your data instead.
Jonathan Machado avatar picture

By Jonathan Machado

February 27, 2020


I came across Vuex ORM when studying the best architecture for an application I was working on and found a post from LinusBorg, member of the Vue core team, mentioning data normalisation as a pattern when dealing with complex/nested data.

The best thing to do is to avoid nested structures... Normalize your data instead.

If you want a detailed explanation about this pattern and the problems it solves I recommend reading a great article from the Redux (React.js) docs : Redux- Normalizing State Shape.

This post assumes you are familiar with Vuex for state management.

What are the advantages?

Trying to update a deeply nested field can quickly become messy. Vuex ORM, however, makes CRUD operations a breeze and it even automatically sets up the mutations and getters you will need!

Let's see some examples of what we can accomplish. If you want to fetch objects from your store you can simply:

import User from '@/models/User'
import Post from '@/models/Post'


// Fetch all posts.
const posts = Post.all()

// Fetch all users.
const users = User.all()

// Get an user with id of 1.
const users = User.find(1)

// alternativelly, using the Getter created automatically by Vue ORM:
const users = store.getters['entities/users/all']()

Or if you want to add a new User:

const newUser = { id: 1, name: 'John' }
User.insert({ data: newUser }

// alternativelly, using the Action created automatically by Vue ORM:
store.dispatch('entities/user/insert', { data: newUser })

You can also insert an array of objects at once!

User.insert({
  data: [
    { id: 1, name: 'John' },
    { id: 2, name: 'Jane' },
    { id: 3, name: 'Johnny' }
  ]
})

Vuex ORM will normalise data received from an API

"Ok, but my backend API doesn't send me normalise data. It sends me everything nested."

APIs frequently send back nested data, that data needs to be transformed into a normalized shape before it can be included in the state tree. The great news is that Vuex ORM will handle this normalisation for us!

If you pass data with relationships to the insert or create method, those relationships will be normalized and inserted to the store.

So, if our API sends you the following data: an array of Posts with nested Authors and Comments:

// array of Post objects
[
  { 
    id: 1,
    title: 'Vuex ORM ',
    body: 'I came across Vue ORM...',
    author: {
      id: 1,
      name: 'Jonathan Machado',
      email: 'machadofjonathan@gmail.com'
    },
    comments: [
        { id: 1,
          body: 'Such a great article, thank you!'
        },
        ...
    ]
  },
  ...
]

Vuex ORM will normalise it for us decoupling each entity into its own Model:

// Inside `store.state.entities`.
{
  posts: {
    data: {
      '1': {
        id: 1,
        title: 'Getting started with Vuex ORM',
        body: 'I came across Vue ORM...',
        author: 1
      },
      ...
    }
  },

  authors: {
    data: {
      '1': {
        id: 1,
        name: 'Jonathan Machado',
        email: 'john@example.com'
      }
    }
  },

  comments: {
    data: {
      '1': {
        id: 1,
        body: 'Such a great article, thank you!',
      },
      ...
    }
  }
}

Awesome!

Setting up

Implementing Vuex ORM is a really simple and straightforward process. You will have to: 1) install Vuex ORM, 2) define the models and 3) register them.

Installation

Since Vue ORM is a Vuex plugin, you must have Vuex installed alongside Vuex ORM:

$ npm install vue vuex @vuex-orm/core --save

Create models

To define a Model, create a class that extends Vuex ORM Model.

As a suggestions you can place the Models inside a models folder.

// models/Post.js
class Post extends Model {
  static entity = 'posts'  // state will be accessible from store.state.entities.posts

  static fields () {
    return {
      id: this.attr(null),
      title: this.attr(''),
      body: this.attr(''),
      comments: this.hasMany(Comment, 'post_id')  // defines the relationship with the Comment Model
    }
  }
}

// models/Comment.js
class Comment extends Model {
  static entity = 'comments'  // state will be accessible from store.state.entities.comments

  static fields () {
    return {
      id: this.attr(null),
      post_id: this.attr(null),
      body: this.attr('')
    }
  }
}

Notice how we're defining the relationship between Post and Comment using hasMany(Comment, 'post_id') .

For more details about Defining Models, Field Types and Relationships check: https://vuex-orm.github.io/vuex-orm/guide/model/defining-models.html

Register models and database

To register defined Models to Vuex store, you must first create a Database, register Models to Database, and then register the Database to the Vuex with the Vuex ORM install method.

That means:

import Vue from 'vue'
import Vuex from 'vuex'
import VuexORM from '@vuex-orm/core'
import User from '@/models/Comment'
import Post from '@/models/Post'

Vue.use(Vuex)

// 1. Create new instance of Database.
const database = new VuexORM.Database()

// 2. Register Models.
database.register(Comment)
database.register(Post)

// 3. Create Vuex Store and register database through Vuex ORM.
const store = new Vuex.Store({
  plugins: [VuexORM.install(database)]
})

export default store

The official Vuex ORM example app implements the recipe above dividing the code into two files database/index.js and store/index.js which I find clearer than having everything in the same file:

// database/index.js

import { Database } from '@vuex-orm/core'
import User from '@/models/Post'
import Todo from '@/models/Comment'

// 1. Create new instance of Database.
const database = new Database()
// 2. Register Models.
database.register(Post)
database.register(Comment)

export default database



// store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
import VuexORM from '@vuex-orm/core'
import database from '@/database'

Vue.use(Vuex)

// 3. Create Vuex Store and register database through Vuex ORM.
const store = new Vuex.Store({
  plugins: [VuexORM.install(database)]
})

export default store

And that's all you need. Now your data will be accessible from within your store (or directly from the Model itself) and can be used in your Vue components:

// components/PostList.vue

import Post from '@/models/Post'

export default {
computed: {
    posts () {
// this query will also fetch the related Comments since we're using .with('comments')
      return Post.query().with('comments').orderBy('id').get()
    }
  },
}

Great!

Vuex ORM provide a sample app which you can use for reference:

vuex-orm/vuex-orm-examples

I also found out that there are some additional plugins you can add to your architecture, depending or your needs. Vuex ORM Axios, for example adds smooth integration between API request call and Vuex ORM.

User.api().get('/api/users')

Additional Plugins

Vuex ORM can be extended via plugins to add additional features. Here is a list of available plugins.

Source: https://vuex-orm.github.io/vuex-orm/guide/digging-deeper/plugins.html#available-plugins

Conclusion

It's important to mention that if you have very simple data without relations, Vuex ORM may be overkill.

However, Vuex ORM brings considerable development convenience benefits!


Stay in touch

Receive updates about new articles as well as curated and helpful content for web devs. No spam, unsubscribe at any time.