Apollo state management in Vue application
Tags: vue, graphql
Published: 2019-05-01 09:32:42

> This article assumes you already know what [GraphQL](https://graphql.org/) and [Apollo client](https://www.apollographql.com/docs/react/) are and you're able to build Vue applications with Vue CLI

## 🤔 Why do we need Apollo local state management?

Imagine a Vue application fetching some data from a REST API. Where do you usually store this data? Probably, the answer would be 'in the local component state' or, if application is big enough, 'in the [Vuex](https://vuex.vuejs.org/) store with the rest of data'. This answer makes sense, because we can have the single source of truth for our application.

Now imagine an application fetching data from a GraphQL endpoint with Apollo client. By default, Apollo will store this data to _Apollo cache_. But what if we have some local application state, stored in Vuex? If we copy data from Apollo cache to Vuex, we're doubling our data. If we leave data from the API in Apollo and local data in Vuex, there will be two sources of truth. What would be a good way to store local data?

Previously there was a library named [apollo-link-state](https://www.apollographql.com/docs/link/links/state) to manage local data. But since Apollo 2.5 release, we don't need it anymore because this functionality is now a part of Apollo core. So, we can simply manage our local state without adding any new dependencies 🎉

## 🏗️ What are we going to build

Let's try to create a simple to-do application with Vue and Apollo client.

I've started with a Vue CLI-powered to-do application with some custom styles. You can find its source code [here](https://github.com/NataliaTepluhina/vue-apollo-todo/tree/initial).

![Initial app view](https://i.imgur.com/837MnGj.png)

## 🔧 Add Apollo to Vue application

First thing we need is to install Apollo client and integrate it to our Vue app. For integration, we will use [vue-apollo plugin](https://vue-apollo.netlify.com/).

To install everything we need, type the following command in the terminal:

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

or

```bash
yarn add vue-apollo graphql apollo-boost
```

Then open `main.js` file and add

```js
// main.js

Vue.use(VueApollo);
```

This way we're adding the vue-apollo plugin to our Vue application.

Now we need to configure our Apollo client. First, let's add an import on the top of `main.js` file:

```js
// main.js

import ApolloClient from 'apollo-boost';
// rest of imports

Vue.use(VueApollo);
```

Then, let's create a client:

```js
// main.js

import ApolloClient from 'apollo-boost';
// rest of imports

Vue.use(VueApollo);

const apolloClient = new ApolloClient({});
```

Add a provider based on this newly created client and inject it to Vue application instance:

```js
// main.js

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

new Vue({
  render: h => h(App),
  apolloProvider, //here goes your Apollo provider
}).$mount('#app');
```

Now we're ready to create an Apollo-powered store.

## 🗃️ Initializing an Apollo cache

We're going to initialize an Apollo cache where we will store our to-do items. Apollo has an `InMemoryCache` constructor to do this:

```js
// main.js

import ApolloClient from 'apollo-boost';
import { InMemoryCache } from 'apollo-cache-inmemory';

// rest of imports

const cache = new InMemoryCache();
```

Now we need to add it to our client:

```js
// main.js

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

So far our `cache` is empty and we're going to add some data to it. But first let's create a local schema. This step could be optional, but just like how a schema is the first step toward defining our data model on the server, writing a local schema is the first step we take on the client.

## 📃 Creating a local schema

Let's think for a minute: what should our to-do item look like? It definitely needs to have some text but what else? Probably we need some property to define if it's already done or not and also an ID to distinguish one todo-item from another. So, it should be an object with three properties:

```js
{
  id: 'uniqueId',
  text: 'some text',
  done: false
}
```

Now we're ready to add item _type_ to GraphQL local schema.

Let's create a new file `resolvers.js` in the `src` folder and add the following code to it

```js
import gql from 'graphql-tag';

export const typeDefs = gql`
  type Item {
    id: ID!
    text: String!
    done: Boolean!
  }
`;
```

`gql` here stands for the JavaScript template literal tag that parses GraphQL query strings.

Awesome! Let's import `typeDefs` and add them to our Apollo client:

```js
// main.js

import ApolloClient from 'apollo-boost';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { typeDefs } from './resolvers';
// rest of imports

const apolloClient = new ApolloClient({
  cache,
  typeDefs,
  resolvers: {},
});
```

> Please note the empty resolvers object here: if we don't assign it to the Apollo client options, it won't recognize the queries to local state and will try to send a request to remote URL instead

Now we need to add some initial data to our cache. To directly write it here, we will use the `writeData` method:

```js
// main.js

// apollo client code

cache.writeData({
  data: {
    todoItems: [
      {
        __typename: 'Item',
        id: 'dqdBHJGgjgjg',
        text: 'test',
        done: true,
      },
    ],
  },
});

// apollo provider code
```

We've just added an array of `todoItems` to our cache `data` and we are saying that every item has a type name of `Item` (specified in our local schema).

Now we're ready to query our local data from our Vue component!

## 🔦 Query local data

First, we need to build a GraphQL query to retrieve the data. Let's create a `graphql` folder, add a `queries.js` file to it and import `graphql-tag` there.

```js
// queries.js

import gql from 'graphql-tag';
```

Now let's write a query:

```js
// queries.js

import gql from 'graphql-tag';

export const todoItemsQuery = gql`
  {
    todoItems @client {
      id
      text
      done
    }
  }
`;
```

So, we defined the name of the query here (`todoItems`) and we specified that this query _should not be executed against remote GraqhQL API_. `@client` directive here tells Apollo client it should fetch results in the local data store.

Finally, we're ready to send the query from the Vue component. To do so, let's open our `App.vue`, import the query constant there:

```html
<script>
import {
  todoItemsQuery,
} from "./graphql/queries.js";

export default {
// rest of App.vue
```

and create an Apollo query in the component:

```js
// App.vue

export default {
  ...
  apollo: {
    todoItems: {
      query: todoItemsQuery
    }
  },
  ...
```

Can you believe it's enough to get everything we need? In fact, yes! This query will effectively fetch our local data and store it to `App.vue`'s `todoItems` property.

## ✏️ Change local data

Now we need to find a way to change the data in store: add new item, delete item or toggle item's `done` property.

We already did change the local data when we were setting initial `todoItems` to the cache. This way is named _direct cache write_ and is useful for initial setup or doing some very simple changes.

For more complicated changes in GraphQL we use `mutations`. So let's go back to our schema and define some mutations there!

```js
// resolvers.js

export const typeDefs = gql`
  type Item {
    id: ID!
    text: String!
    done: Boolean!
  }

  type Mutation {
    changeItem(id: ID!): Boolean
    deleteItem(id: ID!): Boolean
    addItem(text: String!): Item
  }
`;
```

We've just added three mutations to perform different operations with our `todoItems`. Two of them (`changeItem` and `deleteItem`) take the item ID; `addItem` requires a text to create a new item and we're going to generate a unique ID for it.

### Checking/unchecking a todo-item

We will start with `changeItem` mutation. First of all, let's add it to our `queries.js` file:

```js
// queries.js
...
export const checkItemMutation = gql`
  mutation($id: ID!) {
    checkItem(id: $id) @client
  }
`;
```

We defined a _local_ mutation (because we have a `@client` directive here) that will accept an ID. Now, we need a _resolver_: a function that resolves a value for a type or field in a schema.

In our case, resolver will define what changes do we want to make to our local Apollo cache when we have a certain mutation. Local resolvers have the same function signature as remote resolvers `((parent, args, context, info) => data)`. In fact, we will need only `args` (arguments passed to the mutation) and `context` (we will need its `cache` property to read and write data)

Let's start with adding `resolvers` const to our `resolvers.js` file.

```js
// resolvers.js

export const resolvers = {
  Mutation: {
    checkItem: (_, { id }, { cache }) => {},
};
```

So, we created a resolver for `checkItem` and so far it doesn't do anything. We passed an `id` from mutation arguments and `cache` from `context` to it using [ES6 object destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring). Let's read our cache to retrieve current `todoItems`:

```js
// resolvers.js

import { todoItemsQuery } from './graphql/queries';

export const resolvers = {
  Mutation: {
    checkItem: (_, { id }, { cache }) => {
      const data = cache.readQuery({ query: todoItemsQuery });
    },
};
```

As you can see, we imported our `todoItemsQuery` to tell our resolver what exactly we're reading from Apollo cache. Now let's add a logic to change `done` property value to an opposite:

```js
// resolvers.js

import { todoItemsQuery } from './graphql/queries';

export const resolvers = {
  Mutation: {
    checkItem: (_, { id }, { cache }) => {
      const data = cache.readQuery({ query: todoItemsQuery });
      const currentItem = data.todoItems.find(item => item.id === id);
      currentItem.done = !currentItem.done;
    },
};
```

Finally, we need to write our changed data back to the cache and return the value of `currentItem.done`:

```js
// resolvers.js

import { todoItemsQuery } from './graphql/queries';

export const resolvers = {
  Mutation: {
    checkItem: (_, { id }, { cache }) => {
      const data = cache.readQuery({ query: todoItemsQuery });
      const currentItem = data.todoItems.find(item => item.id === id);
      currentItem.done = !currentItem.done;
      cache.writeQuery({ query: todoItemsQuery, data });
      return currentItem.done;
    },
};
```

Now our resolver is ready and we're going to call our mutation from the Vue component. Let's go back to the `App.vue`, import the mutation there and change the `checkItem` method:

```html
<script>
  import {
    todoItemsQuery,
    checkItemMutation,
  } from "./graphql/queries.js";

  export default {
    ...
    methods: {
      checkItem(id) {
        this.$apollo.mutate({
          mutation: checkItemMutation,
          variables: { id }
        });
      },
    }
  };
</script>
```

What's going on here? We call the `$apollo.mutate` method (provided with `vue-apollo` plugin) and we pass the mutation we created earlier in `queries.js` and an `id` variable (ID is passed from the template where we're checking the item):

```html
<ListItem
  v-for="(item, index) in todoItems"
  :key="index"
  :content="item"
  @toggleDone="checkItem(item.id)"
  @delete="deleteItem(item.id)"
/>
```

Now when we're clicking on the checkbox, we will send a mutation that changes our local state. We can see immediately that our `todoItems` array is changed with this mutation so checkbox becomes checked/unchecked.

### Deleting an item

Now we need the way to delete an item. Let's start again with creating a `deleteItem` mutation:

```js
// queries.js

export const deleteItemMutation = gql`
  mutation($id: ID!) {
    deleteItem(id: $id) @client
  }
`;
```

As you can see, it's very similar to the previous one: again, we're passing an ID as a parameter. Now let's add a resolver for it:

```js
// resolvers.js

deleteItem: (_, { id }, { cache }) => {
  const data = cache.readQuery({ query: todoItemsQuery });
  const currentItem = data.todoItems.find(item => item.id === id);
  data.todoItems.splice(data.todoItems.indexOf(currentItem), 1);
  cache.writeQuery({ query: todoItemsQuery, data });
  return true;
},
```

Again, we're reading the `todoItemsQuery` from the cache as a first step and writing it back later (and we're simply returning `true` to show the request was successful). But instead of changing `currentItem` we're just removing it from the `todoItems` array.

Now let's add this mutation to the `App.vue`.

```html
<script>
  import {
    todoItemsQuery,
    checkItemMutation,
    deleteItemMutation
  } from "./graphql/queries.js";

  export default {
    ...
    methods: {
      deleteItem(id) {
        this.$apollo.mutate({
          mutation: deleteItemMutation,
          variables: { id }
        });
      }
    }
  };
</script>
```

Very similar to `checkItem`, isn't it?

### Adding new item

While the two previous mutations were really similar to each other, `addItem` will be different. First of all, we will pass a text, not an ID, and we want to be able to see what is actually added:

```js
// queries.js

export const addItemMutation = gql`
  mutation($text: String!) {
    addItem(text: $text) @client {
      id
      text
      done
    }
  }
`;
```

You can assume the resolver will be more complex as well: we need to generate a unique ID somehow. For this project we're going to use [shortid](https://github.com/dylang/shortid) library:

```bash
npm install shortid
```

or

```bash
yarn add shortid
```

Now let's start building our resolver:

```js
// resolvers.js
import shortid from 'shortid';

export const resolvers = {
  Mutation: {
    ...
    addItem: (_, { text }, { cache }) => {
      const data = cache.readQuery({ query: todoItemsQuery });
      const newItem = {
        __typename: 'Item',
        id: shortid.generate(),
        text,
        done: false,
      };
    },
  }
}
```

As you can see, now we're taking a `text` from our mutation arguments and set a `newItem` `text` property equal to it. For `id` we're generating a new unique ID with `shortid.generate` method. As for `done` property, we always set it to false when creating a new todo-item (because obviously it's not done yet!).

Now we need only to push this newly created item to the `todoItems` array, write the data back to the cache and return a `newItem` as a mutation result.

```js
// resolvers.js

addItem: (_, { text }, { cache }) => {
  const data = cache.readQuery({ query: todoItemsQuery });
  const newItem = {
    __typename: 'Item',
    id: shortid.generate(),
    text,
    done: false,
  };
  data.todoItems.push(newItem);
  cache.writeQuery({ query: todoItemsQuery, data });
  return newItem;
},
```

We're ready to call our `addItem` mutation from the component! Let's import it to `App.vue`...

```html
<script>
  import {
    todoItemsQuery,
    checkItemMutation,
    addItemMutation,
    deleteItemMutation
  } from "./graphql/queries.js";
  ...
</script>
```

...and add it to the `addItem` method:

```js
  addItem() {
    if (this.newItem) {
      this.$apollo.mutate({
        mutation: addItemMutation,
        variables: { text: this.newItem }
      });
      this.newItem = "";
    }
  },
```

`newItem` here represents a string from the input field and we're going to call a mutation only when we have some actual text to send. Also, after we've added a new item, we want to clear an input.

Yay, our application is ready! 🎉

You can find the full application source code [here](https://github.com/NataliaTepluhina/vue-apollo-todo/tree/master).

## Useful links

- [Introduction to GraphQL](https://graphql.org/learn/)
- [Apollo documentation](https://www.apollographql.com/docs/react/)
- [Vue Apollo](https://vue-apollo.netlify.com/)