loading...
Cover image for Wrapping REST API calls with Apollo Client: 'do-it-yourself' approach

Wrapping REST API calls with Apollo Client: 'do-it-yourself' approach

n_tepluhina profile image Natalia Tepluhina ・5 min read

Sometimes, when your application is in the middle of the migration from REST to GraphQL API, you might find yourself in the situation where data your need is split between both APIs. Let's say when you were fetching data from REST API, you were storing it in your application global state - be it Redux, MobX or Vuex. But with the new shiny GraphQL API you don't even need to bother about creating a boilerplate for storing the response - Apollo Client will take care of this process for you! Does it mean with two APIs you need to stick to the old good boring solution and ditch Apollo Client cache? Not at all!

You can wrap your REST API calls with Apollo and store results in Apollo cache too. If you have a large application and have many of them, you can use an apollo-link-rest library for this. In this article, we will create a basic DIY approach to this task to understand better how Apollo resolvers work and how we can use them in our application for good.

What we're going to build?

As an example, we will use a Vue single-page application built on top of Rick and Morty API. The good thing about this API is it has both REST and GraphQL endpoints, so we can experiment with it a bit.

Let's imagine our application was using REST API exclusively. So, on the frontend side, we had a Vuex store and we called axios queries from Vuex actions to fetch characters and episodes from the API.

// Vuex state

state: {
  episodes: [],
  characters: [],
  favoriteCharacters: [],
  isLoading: false,
  error: null
},
// Vuex actions

actions: {
  getEpisodes({ commit }) {
    commit('toggleLoading', true);
    axios
      .get('/episode')
      .then(res => commit('setEpisodes', res.data.results))
      .catch(err => commit('setError', error))
      .finally(() => commit('toggleLoading', false));
  },
  getCharacters({ commit }) {
    commit('toggleLoading', true);
    axios
      .get('/character')
      .then(res => commit('setCharacters', res.data.results))
      .catch(err => commit('setError', err))
      .finally(() => commit('toggleLoading', false));
  },
  addToFavorites({ commit }, character) {
    commit('addToFavorites', character);
  },
  removeFromFavorites({ commit }, characterId) {
    commit('removeFromFavorites', characterId);
  }
}

I don't list Vuex mutations here as they're pretty much intuitive - we assign fetched characters to state.characters etc.

As you can see, we needed to handle the loading flag manually as well as storing an error if something went wrong.

Every single character in characters array is an object:

Character pbject

Now let's imagine our backend developers created a query for us to fetch episodes, but characters still need to be fetched via REST API. So, how we can handle this?

Step 1: extend GraphQL schema

In GraphQL, anything we can fetch from the endpoint, must have a type and be defined in GraphQL schema. Let's be consistent and add characters to schema too. 'But how?' - you might ask, 'schema is defined on the backend!'. That's true but we can extend this schema on the frontend too! This process is called schema stitching. While this step is completely optional, I would still recommend always define GraphQL type definitions for your entities even if they are local. It helps you if you use a code generation to create e.g. TypeScript types from the GraphQL schema and also it enables validation and auto-completion if you use an Apollo plugin in your IDE.

Let's create a new type for characters. We will be using graphql-tag to parse the string to GraphQL type:

// client.js

import gql from "graphql-tag";

const typeDefs = gql`
  type Character {
    id: ID!
    name: String
    location: String
    image: String
  }
`;

As you can see, here we don't use all the fields from the character object, only those we need.

Now we also need to extend a Query type with the GraphQL characters query:

// client.js

import gql from "graphql-tag";

const typeDefs = gql`
  type Character {
    id: ID!
    name: String
    location: String
    image: String
  }
  extend type Query {
    characters: [Character]
  }
`;

To stitch this part of the schema with the schema fetched from the GraphQL endpoint, we need to pass typeDefs to the GraphQL client options:

// client.js

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

const httpLink = createHttpLink({
  uri: "https://rickandmortyapi.com/graphql"
});

const cache = new InMemoryCache();

const typeDefs = gql`
  type Character {
    id: ID!
    name: String
    location: String
    image: String
  }
  extend type Query {
    characters: [Character]
  }
`;

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

Step 2: Writing a query and a resolver

We need to define a GraphQL query with a @client directive to be called when we want to fetch characters. @client directive tells Apollo Client not to fetch this data from the GraphQL endpoint but the local cache. Usually, I keep queries in .gql files and add a graphql-tag/loader to webpack configuration to be able to import them.

// characters.query.gql

query Characters {
  characters @client {
    id
    name
    location
    image
  }
}

But there is one issue: there are no characters in the local cache! How do we 'explain' to Apollo Client where it can get this data? For these purposes, we need to write a resolver. This resolver will be called every time we try to fetch characters to render them in our application.

Let's create a resolvers object and define a resolver for characters query

// client.js

const resolvers = {
  Query: {
    characters() {
      ...
    }
  }
};

What should we do here? Well, we need to perform the same axios call we did in Vuex action! We will map response fields to our GraphQL type fields to make a structure plainer:

// client.js

const resolvers = {
  Query: {
    characters() {
      return axios.get("/character").then(res =>
        res.data.results.map(char => ({
          __typename: "Character",
          id: char.id,
          name: char.name,
          location: char.location.name,
          image: char.image
        }))
      );
    }
  }
};

That's it! Now, when we call GraphQL characters query, our resolver will perform a REST API call and return a result for us. Bonus point: $apollo.queries.characters.loading property will change accordingly when the REST API call is in progress! Also, if some error happens on this call. the Apollo query error hook will be triggered.

Conclusion

As you can see, having a part of the API on the REST endpoint doesn't prevent you from using Apollo Client and its cache. Any REST API call could be wrapped with Apollo resolver and its result can be stored to the Apollo cache which can simplify the migration process.

Discussion

pic
Editor guide
Collapse
bmstschneider profile image
bm-stschneider

Finally. You are the first person that lift the local resolvers conundrum for me. I have read tech.trello.com/adopting-graphql-a... and then apollographql.com/docs/react/local... but I don't use react and I did not know what the equivalent for local state was. And now, local resolvers are deprecated ¬.¬
Can you maybe update this article or make a new one with apollographql.com/docs/react/local... ?

Collapse
adoelcs profile image
Abdoel

Do we need register const resolvers ={...} to ApolloClient ?

*Like typeDefs, right ?