Vue Apollo v4: the first look
Tags: vue, graphql, javascript
Published: 2020-01-03 14:09:08

> This post requires you to be already familiar with the basics of GraphQL, Apollo Client, and Vue. Shameless plug: I've tried to cover this in [my talk on using Vue with Apollo](https://www.youtube.com/watch?v=iJULWNCaLrY). We will also use Vue Composition API. If you're not familiar with this concept, I'd highly recommend reading the [Vue Composition API RFC](https://vue-composition-api-rfc.netlify.com/)

A few weeks ago, an alpha of version 4 of **vue-apollo** (the integration of Apollo client for Vue.js) was released, and I immediately decided to give it a try. What's so exciting in this version? In addition to the existing API, it has a _composables_ option based on Vue Composition API. I've had extensive experience with **vue-apollo** in the past and decided to check how new API feels compared to the previous ones.

## An example we're going to use

To explore the new API, I will use one of the examples already shown in my Vue+Apollo talk - I call it 'Vue Heroes'. It's a straightforward application that has one query for fetching all the heroes from the GraphQL API and two mutations: one for adding heroes and one for deleting them. The interface looks like this:

![Application view](https://i.imgur.com/pugGlMR.png)

You can find the source code with the old Options API [here](https://github.com/NataliaTepluhina/vue-graphql-presentation). The GraphQL server is included; you need to run it to make the application work.

```bash
yarn apollo
```

Now let's start refactoring it to the new version.

## Installation

As a first step, we can safely remove an old version of **vue-apollo** from the project:

```bash
yarn remove vue-apollo
```

And we need to install a new one. Starting from version 4, we can choose what API we're going to use and install the required package only. In our case, we want to try a new _composables_ syntax:

```bash
yarn add @vue/apollo-composable
```

Composition API is a part of Vue 3, and it's still not released now. Luckily, we can use a standalone library to make it work with Vue 2 as well, so for now, we need to install it as well:

```bash
yarn add @vue/composition-api
```

Now, let's open the `src/main.js` file and make some changes there. First, we need to include Composition API plugin to our Vue application:

```js
// main.js

import VueCompositionApi from "@vue/composition-api";

Vue.use(VueCompositionApi);
```

We need to set up an Apollo Client using the new `apollo-composable` library. Let's define a link to our GraphQL endpoint and create a cache to pass them later to the client constructor:

```js
// main.js

import { createHttpLink } from "apollo-link-http";
import { InMemoryCache } from "apollo-cache-inmemory";

const httpLink = createHttpLink({
  uri: "http://localhost:4000/graphql"
});

const cache = new InMemoryCache();
```

Now, we can create an Apollo Client instance:

```js
// main.js

import { createHttpLink } from "apollo-link-http";
import { InMemoryCache } from "apollo-cache-inmemory";
import { ApolloClient } from "apollo-client";

const httpLink = createHttpLink({
  uri: "http://localhost:4000/graphql"
});

const cache = new InMemoryCache();

const apolloClient = new ApolloClient({
  link: httpLink,
  cache
});
```

Creating a client wasn't really different from the previous version of Vue Apollo, and it actually _has nothing to do with Vue_ so far - we're just setting up an Apollo Client itself. What is different is the fact we don't need to create an `apolloProvider` anymore! We simply do natively provide a client to Vue application without an ApolloProvider instance:


```js
// main.js
import { provide } from "@vue/composition-api";
import { DefaultApolloClient } from "@vue/apollo-composable";

new Vue({
  setup() {
    provide(DefaultApolloClient, apolloClient);
  },
  render: h => h(App)
}).$mount("#app");
```

> `provide` here is imported from the `@vue/composition-api` package, and it enables dependency injection similar to the 2.x `provide/inject` options. The first argument in `provide` is a key, second is a value

| 3.x | 4.x composables syntax |
| ----| -----------------|
| [![3.x](https://i.imgur.com/aRt5VCN.png)](https://i.imgur.com/aRt5VCN.png) | [![4.x](https://i.imgur.com/NHIM77t.png)](https://i.imgur.com/NHIM77t.png) |

## Adding a query

To have a list of Vue heroes on the page, we need to create the `allHeroes` query:

```js
// graphql/allHeroes.query.gql

query AllHeroes {
  allHeroes {
    id
    name
    twitter
    github
    image
  }
}
```

We're going to use it in our `App.vue` component so let's import it there:

```js
// App.vue

import allHeroesQuery from "./graphql/allHeroes.query.gql";
```

With the Options API we used this query in the Vue component `apollo` property":

```js
// App.vue

  name: "app",
  data() {...},
  apollo: {
    allHeroes: {
      query: allHeroesQuery,s
    }
  }
```

Now we will modify `App.vue` to make it work with Composition API. In fact, it will require to include one more option to an existing component - a `setup`:

```js
// App.vue

export default {
  name: "app",
  setup() {},
  data() {...}
```

Here, within the `setup` function, we will work with **vue-apollo** composables, and we'll need to return the results to use them in the template. Our first step is to get a result of `allHeroes` query, so we need to import our first composable and pass our GraphQL query to it:

```js
// App.vue

import allHeroesQuery from "./graphql/allHeroes.query.gql";
import { useQuery } from "@vue/apollo-composable";
export default {
  name: "app",
  setup() {
    const { result } = useQuery(allHeroesQuery);

    return { result }
  },
  data() {...}
```

> `useQuery` can accept up to three parameters: first is GraphQL document containing the query, second is variables object, and the third is query options. In this case, we use default options, and we don't need to pass any variables to the query, so we're passing only the first one

What is the `result` here? It's exactly matching the name - it's a result of the GraphQL query, containing `allHeroes` array, but it's also a reactive object - so it's a Vue [`ref`](https://vue-composition-api-rfc.netlify.com/api.html#ref). That's why it wraps the resulting array in the `value` property:

![Result structure](https://i.imgur.com/4dp6jmF.png)

As Vue makes an automatic unwrap for us in the template, we can simply iterate over `result.allHeroes` to render the list:

```html
<template v-for="hero in result.allHeroes">
```

However, the initial value of this array is going to be `undefined` because the result is still loading from the API. We can add a check here to be sure we already have a result like `result && result.allHeroes` but v4 has a useful helper to do this for us - `useResult`. It's a great utility to help you shaping the result you fetched from the API, especially useful if you need to get some deeply nested data or a few different results from one query:

```html
<template v-for="hero in allHeroes">

<script>
import { useQuery, useResult } from "@vue/apollo-composable";
export default {
  setup() {
    const { result } = useQuery(allHeroesQuery);
    const allHeroes = useResult(result, null, data => data.allHeroes)

    return { allHeroes }
  },
}
</script>
```

`useResult` takes three parameters: the result of GraphQL query, a default value (`null` in our case), and a picking function that returns data we want to retrieve from the result object. If the result contains the only one property (like `allHeroes` in our case), we can simplify it a bit:

```js
// App.vue

setup() {
  const { result } = useQuery(allHeroesQuery);
  const allHeroes = useResult(result)

  return { allHeroes }
},
```

The only thing left is to display a loading status when we're actually fetching the data from he API. Aside from the `result`, `useQuery` can return a `loading` as well:


```js
// App.vue
setup() {
  const { result, loading } = useQuery(allHeroesQuery);
  const allHeroes = useResult(result)

  return { allHeroes, loading }
},
```

And we can render it conditionally in out template:

```html
<h2 v-if="loading">Loading...</h2>
```

Let's compare the code we had for v3 with the new one:

| 3.x | 4.x composables syntax |
| ----| -----------------|
| [![3.x](https://i.imgur.com/3ZRjk5w.png)](https://i.imgur.com/3ZRjk5w.png) | [![4.x](https://i.imgur.com/uvIp9J6.png)](https://i.imgur.com/uvIp9J6.png) |

While the new syntax is more verbose, it's also more customizable (to shape the response, we would need to add an `update` property to v3 syntax). I like we can expose `loading` properly for every single query instead of using it as a nested property of the global `$apollo` object.

## Working with mutations

Now let's also refactor mutations we have to the new syntax as well. In this application, we have two mutations: one to add a new hero and one to delete an existing hero:

```js
// graphql/addHero.mutation.gql

mutation AddHero($hero: HeroInput!) {
  addHero(hero: $hero) {
    id
    twitter
    name
    github
    image
  }
}
```

```js
// graphql/deleteHero.mutation.gql

mutation DeleteHero($name: String!) {
  deleteHero(name: $name)
}
```

In the Options API syntax, we were calling mutation as a method of the Vue instance `$apollo` property:

```js
this.$apollo.mutate({
  mutation: mutationName,
})
```

Let's start refactoring with the `addHero` one. Similarly to query, we need to import the mutation to the `App.vue` and pass it as a parameter to `useMutation` composable function:

```js
// App.vue

import addHeroMutation from "./graphql/addHero.mutation.gql";
import { useQuery, useResult, useMutation } from "@vue/apollo-composable";

export default {
  setup() {
    const { result, loading } = useQuery(allHeroesQuery);
    const allHeroes = useResult(result)

    const { mutate } = useMutation(addHeroMutation)
  },
}
```

The `mutate` here is actually a method we need to call to send the mutation to our GraphQL API endpoint. However, in the case of `addHero` mutation, we also need to send a variable `hero` to define the hero we want to add to our list. The good thing is that we can return this method from the `setup` function and use it within the Options API method. Let's also rename the `mutate` function as we'll have 2 mutations, so giving it a more intuitive name is a good idea:

```js
// App.vue

setup() {
  const { result, loading } = useQuery(allHeroesQuery);
  const allHeroes = useResult(result)

  const { mutate: addNewHero } = useMutation(addHeroMutation)

  return { allHeroes, loading, addNewHero }
},
```

Now we can call it in the `addHero` method already present in the component:

```js
export default {
  setup() {...},
  methods: {
    addHero() {
      const hero = {
        name: this.name,
        image: this.image,
        twitter: this.twitter,
        github: this.github,
        github: this.github
      };

      this.addNewHero({ hero });
    }
  }
}
```

As you can see, we passed a variable at the moment mutation is called. There is an alternative way, we can also add variables to the options object and pass it to the `useMutation` function as a second parameter:

```js
const { mutate: addNewHero } = useMutation(addHeroMutation, {
  variables: {
    hero: someHero
  }
})
```

Now our mutation will be successfully sent to the GraphQL server. Still, we also need to update the local Apollo cache on a successful response - otherwise, the list of heroes won't change until we reload the page. So, we also need to read the `allHeroes` query from Apollo cache, change the list adding a new hero and write it back. We will do this within the `update` function (we can pass it with the `options` parameter as we can do with `variables`):

```js
// App.vue

setup() {
  const { result, loading } = useQuery(allHeroesQuery);
  const allHeroes = useResult(result)

  const { mutate: addNewHero } = useMutation(addHeroMutation, {
    update: (cache, { data: { addHero } }) => {
      const data = cache.readQuery({ query: allHeroesQuery });
      data.allHeroes = [...data.allHeroes, addHero];
      cache.writeQuery({ query: allHeroesQuery, data });
    }
  })

  return { allHeroes, loading, addNewHero }
},
```

Now, what's about loading state when we're adding a new hero? With v3 it was implemented with creating an external flag and changing it on `finally`:

```js
// App.vue

export default {
  data() {
    return {
      isSaving: false
    };
  },
  methods: {
    addHero() {
      ...
      this.isSaving = true;
      this.$apollo
        .mutate({
          mutation: addHeroMutation,
          variables: {
            hero
          },
          update: (store, { data: { addHero } }) => {
            const data = store.readQuery({ query: allHeroesQuery });
            data.allHeroes.push(addHero);
            store.writeQuery({ query: allHeroesQuery, data });
          }
        })
        .finally(() => {
          this.isSaving = false;
        });
    }
  }
}
```

In v4 composition API we can simply return the loading state for a given mutation from the `useMutation` function:

```js
setup() {
  ...
  const { mutate: addNewHero, loading: isSaving } = useMutation(
    addHeroMutation,
    {
      update: (cache, { data: { addHero } }) => {
        const data = cache.readQuery({ query: allHeroesQuery });
        data.allHeroes = [...data.allHeroes, addHero];
        cache.writeQuery({ query: allHeroesQuery, data });
      }
    }
  );

  return {
    ...
    addNewHero,
    isSaving
  };
}
```

Let's compare the code we had for v3 with v4 composition API:

| 3.x | 4.x composables syntax |
| ----| -----------------|
| [![3.x](https://i.imgur.com/JlWFPus.png)](https://i.imgur.com/JlWFPus.png) | [![4.x](https://i.imgur.com/2ajU70O.png)](https://i.imgur.com/2ajU70O.png) |

In my opinion, the composition API code became more structured, and it also doesn't require an external flag to keep the loading state.

`deleteHero` mutation could be refactored in a really similar way except one important point: in `update` function we need to delete a hero found by name and the name is only available in the template (because we're iterating through the heroes list with `v-for` directive and we can't get `hero.name` outside of the `v-for` loop). That's why we need to pass an `update` function in the options parameter directly where the mutation is called:

```html
<vue-hero
  v-for="hero in allHeroes"
  :hero="hero"
  @deleteHero="
    deleteHero(
      { name: $event },
      {
        update: cache => updateHeroAfterDelete(cache, $event)
      }
    )
  "
  :key="hero.name"
></vue-hero>

<script>
  export default {
    setup() {
      ...

      const { mutate: deleteHero } = useMutation(deleteHeroMutation);
      const updateHeroAfterDelete = (cache, name) => {
        const data = cache.readQuery({ query: allHeroesQuery });
        data.allHeroes = data.allHeroes.filter(hero => hero.name !== name);
        cache.writeQuery({ query: allHeroesQuery, data });
      };
      return {
        ...
        deleteHero,
        updateHeroAfterDelete,
      };
    }
  }
</script>
```

## Conclusions

I really like the code abstraction level provided with vue-apollo v4 composables. Without creating a `provider` and injecting an `$apollo` object to Vue instance, there will be easier to mock Apollo client in unit tests. The code also feels more structured and straightforward to me. I will be waiting for the release to try in on the real-world projects!