Vue and wine

Graphql workshop

What is graphql?

Syntax that describes how to ask for data

query {
  authors {
    name
  }
}
{
  "data": {
    "authors": [
      {
        "name": "Natalia"
      }
    ]
  }
}
query {
  authors {
    name
    age
  }
}
{
  "data": {
    "authors": [
      {
        "name": "Natalia",
        "age": 35
      }
    ]
  }
}

No overfetching
no underfetching

Schema and Type System

Basic Example

Type relationship

type User {
  name: String!;
  age: Int;
}
type Comment {
  title: String!;
  author: User!;
}

Scalar types

  • Int

  • Float

  • String

  • Boolean

  • ID

Meet our first schema!

  • Go to the graphql-server folder

  • Run npm run apollo or yarn apollo
  • Open http://localhost:4000/graphql in the browser

Let's make a query

Moar wines!

Installation

npm install --save vue-apollo graphql apollo-boost

or

yarn add vue-apollo graphql apollo-boost
// main.js

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

Vue.use(VueApollo)
// main.js

import { ApolloClient } from 'apollo-client'
import { HttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory'

const httpLink = new HttpLink({
  uri: 'http://localhost:4000/graphql'
})

const defaultClient = new ApolloClient({
  link: httpLink,
  cache: new InMemoryCache()
})
// main.js

const apolloProvider = new VueApollo({
  defaultClient,
})

new Vue({
  el: '#app',
  apolloProvider,
  render: h => h(App),
})

configuring webpack

module.exports = {
  chainWebpack: config => {
    config.module
      .rule('graphql')
      .test(/\.(graphql|gql)$/)
      .use('graphql-tag/loader')
      .loader('graphql-tag/loader')
      .end()
  }
}

IDE integration

VS Code: Apollo GraphQL

Webstorm: JS GraphQL

// apollo.config.js

module.exports = {
  client: {
    service: {
      name: 'Wines',
      url: 'http://localhost:4000/graphql'
    },
    includes: ['./src/**/*.gql']
  }
}

VS Code

// .graphqlconfig

{
  "name": "Wines",
  "extensions": {
    "endpoints": {
      "Default GraphQL Endpoint": {
        "url": "http://localhost:4000/graphql",
        "headers": {
          "user-agent": "JS GraphQL"
        },
        "introspect": false
      }
    }
  }
}

WEbstorm

dev tools

Finally, let's code

vue app

create query file

import and use your query

import allWinesQuery from '../graphql/allWines.query.gql'
...
apollo: {
  allWines: {
    query: ...,
  }
},

what if we need different name?

import allWinesQuery from '../graphql/allWines.query.gql'
...
wines: {
  query: allWinesQuery,
  update(data) {
    return data.allWines
  },
},

What if we have an error 😱

  apollo: {
    wines: {
      query: ...,
      update(data) {...},
      error(error) {
        console.log(error)
      }
    }
  }

handling loading state

// Global
v-if="$apollo.loading"

// For the certain query
v-if="$apollo.queries.wines.loading"

Query component

<ApolloQuery :query="require('../graphql/allWines.query.gql')">
  <template v-slot="{ result: { error, data }, isLoading }">
    ...
  </template>
</ApolloQuery>

passing a parameter to query

// getWine.query.gql

query GetWine($id: ID!) {
  getWine(id: $id) {
    id
	...
  }
}

write a query to fetch a wine with a given id

using options or component syntax is up to you

Now let's create our own wine

mutation AddWine($wine: WineInput!) {
  addWine(wine: $wine) {
	
  }
}

Don't repeat yourself!

// wineShort.fragment.gql

fragment WineShort on Wine {
  id
  title
  year
  variety
}

Don't repeat yourself!

#import './wineShort.fragment.gql'

query AllWines {
  allWines {
    ...WineShort
  }
}

B-b-but wines are not updated!

this.$apollo
  .mutate({
    mutation: ...,
    variables: ...,
    update: (store, {data: {addWine}}) => {}
  });

we can read / write queries

update: (store, { data: { addWine } }) => {
  const data = store.readQuery({ query: allWinesQuery })
  // CHANGE YOUR DATA HERE!
  store.writeQuery({ query: allWinesQuery, data })
}
Wine.vue

deleteWine()

mutation components

<ApolloMutation
  :mutation="require('../graphql/addWine.mutation.gql')"
  :update="updateCache"
  @done="closeModal"
>
  <template v-slot="{ mutate, loading, error }">
    <WineForm
      @submit="mutate({ variables: { wine: $event } })"
      @cancel="closeModal"
    />
  </template>
</ApolloMutation>

subscriptions

const wsLink = new WebSocketLink({
  uri: 'ws://localhost:4000/graphql',
  options: {
    reconnect: true
  }
})

subscriptions

//wineSub.subscription.gql

subscription WineSub {
  wineSub {
    ...WineShort
  }
}

subscribe to more

subscribeToMore: {
  document: wineSubscription,
  updateQuery: (
    previous,
    { subscriptionData: { data: { wineSub } } }
  ) => {
          // CHANGE PREVIOUS STATE HERE
          return previous
        }
      },

Let's have some favorite wines

 
cache = new InMemoryCache()

cache.writeData({
  data: {
    favoriteWines: ['9X9d6n2r7']
  }
})

local schema

 
extend type Query {
  favoriteWines: [ID!]!
}

extend type Wine {
  isInFavorites: Boolean
}

extend type Mutation {
  addOrRemoveFromFavorites(id: ID!): [ID]
}

local query

 
query FavoriteWines {
  favoriteWines @client
}

local query

 
query AllWines {
  allWines {
    ...WineShort
    isInFavorites @client
  }
}

local resolvers

 
export const resolvers = {
  Wine: {
    isInFavorites: (wine, _, { cache }) => {
      const { favoriteWines } = cache.readQuery({ query: getFavoriteWines })
      return favoriteWines.includes(wine.id)
    }
  },
}

local mutation

 
mutation ToggleFavorites($id: ID!) {
  addOrRemoveFromFavorites(id: $id) @client
}

(and its resolver)

 
Mutation: {
  addOrRemoveFromFavorites: (_, { id }, { cache }) => {
    const { favoriteWines } = cache.readQuery({ query: getFavoriteWines })
    const data = {
      favoriteWines: favoriteWines.includes(id)
        ? favoriteWines.filter(i => i !== id)
        : [...favoriteWines, id]
    }
    cache.writeQuery({ query: getFavoriteWines, data })
    return data.favoriteWines
  }
}

in component

 
toggleFavorites() {
  this.$apollo.mutate({
    mutation: toggleFavoritesMutation,
    variables: { id: this.wine.id },
    refetchQueries: [{ query: allWinesQuery }]
  })
}

VueAndWine GraphQL workshop

By Natalia Tepluhina

VueAndWine GraphQL workshop

Slides to use during GraphQL workshop

  • 1,011