Ben Klein
Posted on May 28, 2018
TL;DR: I made dis: https://vuex-orm.github.io/vuex-orm-graphql/
This is my first article on dev.to and I want to use this to share my current open source project with this amazing community :)
The GraphQL Plugin for Vuex-ORM and this article is powered by the Germany based i22 Digitalagentur GmbH.
The beginning of journey
In my current side project, I was using a JSON/REST API and attached the Frontend Single Page Application via JSData. A setup that never made me really happy and brought a lot of issues.
Then I discovered Vuex-ORM which brings everything I need to work with models in Vue and also has real reactivity, is blazing fast and uses Vuex. I felt like I replace JSData with Vuex-ORM. But: There have been no plugins to attach an API. I don't want to have too much boilerplate in my components and I don't like to fiddle around with the specifics of the communication with the API. Integrating your Single Page Application with your API can be a pain. But it shouldn't.
At same time earlier this year, I've learned about GraphQL in my new full time job. And it was amazing. After some playing around, I've stated to love it and felt like I could replace the REST/JSON API of my side project with GraphQL.
Then we were starting a project in my full-time job at i22 with a very similar setup like my side project, so I decided to give Vuex-ORM a try. But we have committed to use GraphQL (like in all our other projects). Like there was no JSON/REST plugin for Vuex-ORM there was also no GraphQL plugin. I thought, I could write one. Shouldn't be that difficult, I thought: We already have our model definitions and a GraphQL API, I just could transform the model definitions into a GraphQL query. I could use the apollo-client lib. This plugin should make the communication with the API a pleasure. No boilerplate, no fiddling together JSON structures or whatever. So let's start!
It was a lot more difficult then I thought, but after some weeks of development, I had a basically working plugin which has a minimal component boilerplate and just works with reactivity and all that fancy stuff.
Time to go the next step: Migrate my side project from JSON/REST + JSData to Vuex-ORM + GraphQL. While doing that I discovered some bugs in my plugin and fixed them, wrote some more tests and added some missing features.
Today, some months of finetuning, testing and a lot of help from Kia, the very kind and smart maintainer of Vuex-ORM, later, I have a well working GraphQL Plugin for Vuex-ORM with all basic features, documentation and two of my current projects using it. It works like a charm. I'm very happy with it currently :)
Now it's time to share my work with the community. In the rest of this article I will show you how the Vuex-ORM-GraphQL plugin works and how you can use it in your project. Please remember: This is just a basic example. For more details, please take a look in the documentations both of Vuex-ORM and Vuex-ORM-GraphQL. Have fun!
How it works
I assume you have experience with Vue and maybe GraphQL.
Vuex-ORM
First of all we have to setup Vuex-ORM, which is pretty simple.
yarn add @vuex-orm/core
Then create a store.js
and a models
directory somewhere in your app.
Let's assume we're building a simple blog. There are posts
and comments
. So we need two models. Let's create them in our new models
directory:
models/post.js
:
import { Model } from '@vuex-orm/core';
import Comment from './comment';
/**
* The Vuex-ORM post model
*/
export default class Post extends Model {
// Tell Vuex-ORM the path where the records should be stored in Vuex
static entity = 'posts';
// Tell Vuex-ORM-GraphQL to eagerly load all comments when we fetch a post.
static eagerLoad = ['comments'];
// Setup the fields and relations for Vuex-ORM
static fields () {
return {
id: this.increment(),
title: this.string(''),
content: this.string(''),
publishedAt: this.string(''),
comments: this.hasMany(Comment, 'postId')
}
}
}
models/comment.js
:
import { Model } from '@vuex-orm/core';
import Post from './post';
/**
* The Vuex-ORM comment model
*/
export default class Comment extends Model {
// Tell Vuex-ORM the path where the records should be stored in Vuex
static entity = 'comment';
// Setup the fields for Vuex-ORM
static fields () {
return {
id: this.increment(),
author: this.string(''),
content: this.string(''),
publishedAt: this.string(''),
postId: this.number(0),
post: this.belongsTo(Post, 'postId')
}
}
}
As you can see, setting up a model for Vuex-ORM is very easy. Now we have to setup our Vuex store like this:
store.js
import Vue from 'vue';
import Vuex from 'vuex';
import VuexORM from '@vuex-orm/core';
import Post from './models/post';
import Comment from './models/comment';
// Setup Vuex
Vue.use(Vuex);
// Setup Vuex-ORM database
const database = new VuexORM.Database();
database.register(Post, {});
database.register(Comment, {});
// Create Vuex Store and register the Vuex ORM plugin.
export default new Vuex.Store({
plugins: [VuexORM.install(database)]
});
After that we can already create new records, find them, change them and delete them in a component:
import Post from 'store/models/post';
Post.create({
title: 'Example Blog Post',
content: 'Lorem ipsum dolor sit amet',
publishedAt: (new Date()).toISOString()
});
const allPosts = Post.all();
Vuex-ORM GraphQL Plugin
In the next step we setup my new GraphQL Plugin for Vuex-ORM, which is amazing simple, because the plugin hides all the complexity of the apollo-http-link, apollo-client and so on from you. It's designed to be installed and just work:
yarn add @vuex-orm/plugin-graphql
Change the store.js
like this:
// ...
database.register(Post, {});
database.register(Comment, {});
// --8<-------------
// This is the new part
import installVuexORMGraphQL from '@vuex-orm/plugin-graphql';
VuexORM.use(installVuexORMGraphQL, {
database: database,
debug: process.env.NODE_ENV !== 'production'
});
// --8<-------------
// Create Vuex Store and register the Vuex ORM plugin.
export default new Vuex.Store({
plugins: [VuexORM.install(database)]
});
Not much magic here, we just register the Vuex-ORM-GraphQL plugin as a Vuex-ORM plugin and pass the database. Nothing more to do. As I said: Setup I super easy ;)
Store vs Persistence Actions
While using Vuex-ORM with the GraphQL plugin you have to distinct between two types of Vuex actions:
- Store actions: Retrieve data from or save data to the Vuex Store (
Vue Component <--> Vuex Store
) - Persistence actions: Load data from or persist data to the GraphQL API (
Vuex Store <--> GraphQL Server
)
The following table lists all actions and what they do:
CRUD | Vuex Only | Persist to GraphQL API |
---|---|---|
READ |
find() , all() , query()
|
fetch() |
CREATE |
create() or insert()
|
$persist() |
UPDATE | $update() |
$push() |
DELETE | $delete() |
$destroy() |
Example
After our setup we can use Vuex-ORM to fetch data from the GraphQL API and display it reactively:
<template>
<div class="blog">
<article v-for="post in posts" :key="post.id" class="blog__post">
<h2>{{post.title}}</h2>
<small>{{post.publishedAt}}</small>
<p>{{post.content}}</p>
<a href="#" @click.prevent="destroy(post)">Delete this post</a>
<hr />
<section class="comments">
<h3>Comments</h3>
<article v-for="comment in posts.comments" :key="comment.id" class="comments__comment">
<h4>From {{comment.author}}</h4>
<p>{{comment.content}}</p>
</article>
</section>
</article>
</div>
</template>
<script>
import Post from 'data/models/post';
export default {
computed: {
// Returns all posts with reactivity.
posts: () => Post.all()
},
async mounted() {
// Load all posts form the GraphQL API.
await Post.fetch();
},
methods: {
// Deletes the post from Vuex Store and from the server.
async destroy(post) {
post.$deleteAndDestroy();
}
}
}
</script>
And that's all the required code to load the blog posts and comments from the server, display them and allow the user to delete the post.
GraphQL Queries
The code above generates following GraphQL query for fetch
:
query Posts {
posts {
nodes {
id
content
title
publishedAt
comments {
nodes {
id
author
content
publishedAt
postId
}
}
}
}
}
And the following GraphQL mutation for destroy
:
mutation DeletePost($id: ID!) {
deletePost(id: $id) {
id
}
}
Conclusion
There's still a lot of work to do: The code is a mess at some points, there are some missing tests, subscriptions are not implemented yet, there could be much more configurability and the documentation is not finished yet. But I thought it's time to share my work with the community to get feedback and hopefully get some contributions to bring the plugin to a stable version 1.0.0.
Thanks for reading.
PS: The side project launches soon. I will make a post about it when the time case come ;)
Posted on May 28, 2018
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.