Vue SPA with Rails JSON:API using Graphiti
Tags: javascript, ruby, vue, webdev
Published: 2018-12-05 17:30:42

---
title: Vue SPA with Rails JSON:API using Graphiti
published: true
description: How to build a Vue SPA with a JSON:API compliant Rails API using Graphiti
tags: Javascript, Ruby, Vue, webdev
---

Standards are good, they make our lives easier. [Graphiti](https://graphiti-api.github.io/graphiti/guides/) and it's client library [Spraypaint](https://graphiti-api.github.io/graphiti/js/) make it easy to build [JSON:API](https://jsonapi.org/) compliant APIs that integrate seamlessly with front-end frameworks like Vue.

I’m using graphiti in a production application to serve JSON requests to Vue components embedded in our Rails views. It's been reliable, flexible and a pleasure to use.

In this tutorial we’ll walk through setting up Vue as an SPA with a JSON:API compliant Rails 5 API using graphiti. You can clone the demo app to see the finished product.

```
# follow along
git clone git@github.com:mikeeus/demos-rails-webpack.git
cd demos-rails-webpack
git checkout ma-vue-graphiti
```

## Set up Rails API with Webpacker

Create Rails app with webpacker and Vue. I use postgresql but you can use any database you like.

```
mkdir rails-vue
rails new . --webpack=vue —database=postgresql
rails db:create db:migrate
```

And… done! That was easy right? Now we can move on to setting up graphiti to handle parsing and serializing our records according to the JSON API spec.

## Set up Graphiti

Install graphiti, you can find the [full instructions in the docs](https://graphiti-api.github.io/graphiti/guides/getting-started/installation).  We’ll need to add the following gems.

```ruby
# The only strictly-required gem
gem 'graphiti'

# For automatic ActiveRecord pagination
gem 'kaminari'

# Test-specific gems
group :development, :test do
  gem 'rspec-rails'
  gem 'factory_bot_rails'
  gem 'faker'
  gem 'graphiti_spec_helpers'
end

group :test do
  gem 'database_cleaner'
end
```

We’ll need to add `Graphiti::Rails` to our Application controller so graphiti can handle parsing and serializing our requests. And we’ll register the `Graphiti::Errors::RecordNotFound` exception so we can return 404.

```ruby
# app/application_controller.rb
class ApplicationController < ActionController::API
    include Graphiti::Rails

    # When #show action does not find record, return 404
    register_exception Graphiti::Errors::RecordNotFound, status: 404

    rescue_from Exception do |e|
        handle_exception(e)
    end
end
```

Now lets create a Post model.

```
rails g model Post title:string content:string
rails db:migrate
```

We’ll also need to create a PostResource for graphiti and a controller to handle requests. Graphiti has a [generator](https://graphiti-api.github.io/graphiti/guides/concepts/resources#generators) that makes it easy to set this up.

```
rails g graphiti:resource Post -a index
```

We’re going to declare our attributes and add `ActionView::Helpers::TextHelper` to format our Post content using `simple_format` so we can render it nicely on our client.

```ruby
class PostResource < Graphiti::Resource
    include ActionView::Helpers::TextHelper

    self.adapter = Graphiti::Adapters::ActiveRecord
    primary_endpoint '/api/v1/posts'

    attribute :title, :string
    attribute :content, :string do
        simple_format(@object.content)
    end
end
```

The generator will also create specs and a controller at `app/controllers/posts_controller.rb`. We’re going to move that to a namespaced folder `app/api/v1` which will allow us to manage API versions in the future.

```ruby
# app/controllers/api/v1/posts_controller.rb
module Api
    module V1
        class PostsController < ApplicationController
            def index
                posts = PostResource.all(params)
                render jsonapi: posts
            end
        end
    end
end
```

We use `render jsonapi: posts` to render the posts according to the JSON:API spec so we can parse it on our client using graphiti's js client [spraypaint](https://graphiti-api.github.io/graphiti/js/).

Now let’s add the route.

```ruby
# config/routes.rb
Rails.application.routes.draw do
    namespace :api do
        namespace :v1 do
            resources :posts, only: :index
        end
    end
end
```

Alright looking good! All we need now is a client to consume our API.

## Setup Vue

Webpacker comes with a generator for vue which you can [see in the docs](https://github.com/rails/webpacker#vue). It makes it super easy to add Vue or any other front-end framework like React or Angular to our application.

```
bundle exec rails webpacker:install:vue
```

Running the above will generate files at `app/javascript`

We’re going to edit `app/javascript/packs/application.js` so that we can render our App.vue component.

```javascript
// app/javascript/packs/application.js

import Vue from 'vue/dist/vue.esm'
import App from '../app.vue'

document.addEventListener('DOMContentLoaded', () => {
    const app = new Vue({
        el: '#app',
        components: { App }
    })
})
```

For now we can disregard the Vue component, we’ll fill it in later once we’ve setup our resources and endpoints.

## Setup Rails to Serve Static Content

We can’t use our ApplicationController to serve our `index.html` page since it inherits from `ActionController::Api` and we want to keep it that way since our other controllers will inherit from it.

In order to serve our index page for the SPA, we’ll use a `PagesController` that inherits from `ActionController::Base` so it can serve html files without issue.

```ruby
# app/pages_controller.rb
class PagesController < ActionController::Base
  def index
  end
end
```

Next we’ll add a route for our homepage and redirect all 404 requests to it so our SPA can take care of business.

```ruby
# config/routes.rb
Rails.application.routes.draw do
    root 'pages#index'
    get '404', to: 'pages#index'

    namespace :api do
        # ...
    end
end
```

Looking good, friends! Now let’s add our index.html page which will render our Vue component.

```html
# app/views/pages/index.html
<%= javascript_pack_tag 'application' %>
<%= stylesheet_pack_tag 'application' %>

<div id="app">
  <app></app>
</div>
```

It’s super simple: it just pulls in our javascript and stylesheets compiled by webpacker. Then we add a div with id=“app” and a nested `<app></app>` so our Vue wrapper component can pick it up and render the main component.

This is the only Rails view we need to write for our application to work.

## Create Models on Client

Usually when I build an SPA I’ll write services that use libraries like axios to make Ajax requests to the backend. Graphiti comes with a client library called [spraypaint](https://graphiti-api.github.io/graphiti/js/) that handles parsing and serializing JSON:API payloads. It supports including associations, advanced filtering, sorting, statistics and more.

Let’s set it up!

```
yarn add spraypaint isomorphic-fetch
```

Next let’s create an `ApplicationRecord` class that will store our spraypaint configuration.

```javascript
// app/javascript/models/application_record.js

import { SpraypaintBase } from 'spraypaint';

export const ApplicationRecord = SpraypaintBase.extend({
  static: {
    baseUrl: '',
    apiNamespace: '/api/v1',
  }
})
```

We set the `baseUrl`  and `apiNamespace` to ‘’ and ‘/api/v1‘ respectively so that spraypaint uses relative paths and avoids CORS requests. It also namespaces our requests so we can manage API versions easily.

Now the Post model

```javascript
// app/javascript/models/post.model.js

import { ApplicationRecord } from './application_record';

export const Post = ApplicationRecord.extend({
    static: {
        jsonapiType: 'posts'
    },

    attrs: {
        id: attr(),
        title: attr(),
        content: attr()
    },

    methods: {
        preview() {
            return this.content.slice(0, 50).trim() + '...'
        }
    }
})
```

We declare the `id`, `title` and `content` attributes. We also add a method to return a truncated preview of the content to show how we declare methods.

The `jsonapiType` property is needed to generate the endpoint and parse and serialize the JSON payload.

Now we’re ready to hook up the client to the API.


## Hook up SPA

To hook everything up we’ll create a Vue component that uses our spraypaint models to communicate with our endpoints.

```vue
// app/javascript/app.vue

<template>
<div>
    <h1>Posts</h1>
    <div v-if="error" class="error">{{error}}</div>
    <div class="loading" v-if="loading">Loading...</div>
    <ul>
        <li v-for="post of posts" :key="post.id">
            <h3>{{post.title}}</h3>
            <p v-html="post.preview()"></p>
        </li>
    </ul>
</div>
</template>

<script>
import {Post} from './models/post.model'

export default {
    data: function () {
        return {
            posts: [],
            error: null,
            loading: true
        }
    },

    created() {
        Post.all()
            .then(res => {
                this.posts = res.data
                this.loading = false
            })
            .catch(err => {
                this.error = err
                this.loading = false
            })
        }
}
</script>

<style scoped>
h1 {
    text-align: center;
}
ul {
    list-style: none;
}
</style>
```

Marvellous! If we add some posts in the console and run the application we will  see the posts load and render in the page.

Notice that we import our Post model and use it in our `created()` call like it was a Rails model. Calling `Post.all()` returns a promise that we can chain to set our `posts` and `loading` data properties. The spraypaint model can chain more useful methods like `where` and `page`.

```javascript
Post.where({ search: 'excerpt' })
    .stats({ total: 'count' })
    .page(1)
    .per(10)
    .order({ created_at: 'desc' })
    .all()
    .then(res => ...)
```

Spraypaint is a very powerful library that supports pagination, sorting, statistics, complex filtering and much more. You can check out the [spraypaint docs](https://graphiti-api.github.io/graphiti/js/) for detailed guides.

## Conclusion

Standards are good. Vue is awesome, and so is Rails. Gems and libraries like Graphiti and Spraypaint make it super easy to build scalable REST APIs that comply with said standards and integrate seamlessly with frameworks like Vue.

I hope you enjoyed the article, don’t forget to like if you did. I would love to hear your thoughts or suggestions for other articles. Just leave a comment below :)