GraphQL made easy with Apollo and Vue

@Akryum

github.com/Akryum

Guillaume Chau

Apollo

GraphQL

Vue

Meteor Development Group

meteor.io

github.com/apollographql

  • GraphQL Client
    • JavaScript
    • Android
    • iOS
  • Server Tools
    • express
    • koa
    • hapi
    • restify
    • AWS lambda

Apollo Optics

apollodata.com/optics

Apollo Dev Tools!

Apollo Client

Query

Mutation

Subscription (Web socket)

.gql

Observable

query

Normalized Cache

Getting Started

import { ApolloClient, createNetworkInterface }
  from 'apollo-client'

// Create the apollo client
const apolloClient = new ApolloClient({
  networkInterface: createNetworkInterface({
    uri: 'http://localhost:3000/graphql',
  }),
  connectToDevTools: true,
})

vue-apollo

Install

npm i -S apollo-client vue-apollo

yarn add apollo-client vue-apollo

Enable

import Vue from 'vue'
import VueApollo from 'vue-apollo'

// Install the vue plugin
Vue.use(VueApollo)

Create a provider

const apolloProvider = new VueApollo({
  defaultClient: apolloClient,
})

// Add it to the root instance
new Vue({
  el: '#app',
  apolloProvider,
  render: h => h(App),
})

Inside your Component

<template>
  <nav>
    <a v-for="post of posts">{{post.title}}</a>
  </nav>
</template>

<script>
export default {

}
</script>

Write a GraphQL document

import gql from 'graphql-tag'

const POSTS_QUERY = gql`
query Posts {
  posts {
    title
  }
}
`

Use the new apollo option

export default {
  data () {
    return {
      posts: [],
    }
  },
  // Apollo-specific options
  apollo: {
    posts: POSTS_QUERY,
  },
}

GraphQL Webpack Loader

export default {
  rules: [
    // ...
    {
      test: /\.(graphql|gql)$/,
      exclude: /node_modules/,
      loader: 'graphql-tag/loader',
    },
  ]
}

Documents in separate files

import POSTS_QUERY from '../graphql/Posts.gql'

export default {
  // ...
  apollo: {
    posts: POSTS_QUERY,
  },
}

Another document

query Post ($id: ID!) {
  post (id: $id) {
    title
    contents
    publishedDate
    tags
    author {
      name
      company
    }
  }
}

Variables & Loading

export default {
  props: ['id'],
  data () {
    return {
      post: null,
      loading: 0,
    }
  },
  apollo: { /* ... */ },
}

Variables & Loading

apollo: {
  post: {
    query: POST_QUERY,
    variables () {
      return { id: this.id }
    },
    loadingKey: 'loading',
  },
},

More Features

  • Mutations
  • Pagination (Fetch more)
  • Real-time Subscriptions
  • Multiple Apollo clients
  • SSR support

vue-supply

Helping you

consume Reactive Data

Efficiently

How does it work?

Reactive

Data

Component

Created /

Activated

consumers++
consumers > 0

Now Active

Supply

Subscribe

How does it work?

Reactive

Data

Component

Destroyed /

Deactivated

consumers--
consumers === 0

Now Inactive

Supply

Unsubscribe

Install

npm i -S vue-supply

yarn add vue-supply

Enable

import Vue from 'vue'
import VueSupply from 'vue-supply'

Vue.use(VueSupply)

Create a base Supply Definition

import { Supply } from 'vue-supply'
import apolloProvider from '../apollo-provider'
export default {
  extends: Supply,
  apolloProvider,
  apollo: {
    $skipAll () {
      return !this.active
    },
  },
}

Extend it in another

import Base from './BaseApolloSupply.js'

export default {
  extends: Base,
  data () {
    return { posts: [] },
  },
  apollo: {
    posts: {
      query: POSTS_QUERY,
      loadingKey: 'loading',
    },
  },
}

GraphQL Subscription

apollo: {
  posts: {
    query: POSTS_QUERY,
    subscribeToMore: {
      document: POST_ADDED_QUERY,
      updateQuery (previousResult, { sub }) {
        return {
          posts: [
            ...previousResult.posts,
            sub.data.newPost,
          ],
        }

Register the supply

import { register } from 'vue-supply'

import Posts from './PostsSupply.js'

register('Posts', Posts)

Consume in Component

import { use } from 'vue-supply'

export default {
  // This component now uses Posts supply
  mixins: [use('Posts')],

  // Use the values in computed properties
  computed: {
    filteredPosts () {
      return this.$supply.Posts.posts.filter(...)
    },
  },
}

Consume in Component

<template>
  <section>
    <div v-if="!$supply.Posts.ready">
      Loading...
    </div>
    <div v-for="tag of $supply.Posts.posts">
      {{ post.title }}
    </div>
  </section>
</template>

Inject the supply in the Vuex Store

export default {
  supply: {
    use: ['Posts'],
    inject: ({ Posts }) => ({
      getters: {
        'all-posts': () => Posts.items,
      },
    }),
  },
  getters: {
    'posts-count': (state, getters) => {
      return getters['all-posts'].length
    },
  },
}

Augment the Vuex Store

import { injectSupply } from 'vue-supply'
import storeOptions from './vuex/store.js'

// Prevent duplicate supplies!
const supplyCache = {}

const o = injectSupply(storeOptions, supplyCache)
const store = new Vuex.Store(o)

new Vue({
  // ...
  store,
  supplyCache,
}),

Consume in Components

import { use } from 'vue-supply'
import { mapGetters } from 'vuex'

export default {
  // This component now uses Posts supply
  mixins: [use('Posts')],

  // Getter that uses the supply
  computed: mapGetters({
    items: 'all-posts',
  }),
}

Consume in the store

supply: {
  use: ['Posts'],
  inject: ({ Posts }) => ({
    actions: {
      'subscribe-action' () {
        Posts.grasp()
        return Posts.ensureReady()
      },
      'unsubscribe-action' () {
        Posts.release()
      },
    }
  }),
}

Consume in the store

export default {
  actions: {
    async foo ({ dispatch, getters }) {
      await dispatch('subscribe-action')
      const data = getters['all-posts']
      dispatch('unsubscribe-action')
    },
  },
}

SSR-friendly!

Resources

Thank you!

@Akryum

github.com/Akryum

Guillaume Chau

[EN] Vue + Apollo + GraphQL

By Guillaume Chau

[EN] Vue + Apollo + GraphQL

Talk from Vue Conf 2017. Demo app source code: https://github.com/Akryum/vueconf-2017-demo

  • 34,836