Apr 2020

How to fetch data from a GraphQL service

Let's explore how to consume data from a GraphQL API. We will build a small project, without any frontend framework, just vanilla JavaScript. The task is simple - get a list of items, but only the fields that need to be displayed on initial load. Next, get further details, but only after user interaction. I will be using a public GraphQL API created by Trevor Blades, providing information about countries.

The API documentation

If you ever worked with a REST API before, you sure know what to expect from the documentation pages. Usually a list of CRUD endpoints, with examples of the payload that you might need to send along with sample responses, status codes, headers etc. Sometimes API developers would add interactive documentation with something like Swagger, so you could try the API right in your browser with very little effort (even though often times the documentation would become stale and out of sync with the actual API).

GraphQL documentation on the other hand requires a bit of a the mental switch - GraphQL APIs are organized around types and fields, instead of endpoints. GraphQL is structured hierarchically like a tree, built of different types. You can think of a type, as an object with defined set of fields. This allows for easy exploration in an interactive tool like GraphiQL.

You can click through the available types and their fields, until you reach a leaf - that is a filed that has no children. These types are called scalar types, and you can see them as sort of building blocks. The scalar types that come out of the box with GraphQL are limited number: Int, Float, String, Boolean, and ID.

You can head to the API documentation page. In the docs tab you can click around and see what is available for us to fetch from this API.

Interactive documentation example

We have the following types available: continents, continent, countries, country, languages and language.

When you click on a query, for example country, the response is displayed, in case it is an object of type Country. Whenever you see an exclamation mark after the type, it means it is not nullable. For example, in the following languages: [Language!]! the language field could possibly come back as an empty array but never as null. We can also be sure that each item in the array will only ever be of type Language and never null. There is more to explore, but for now we have enough understanding of the GraphQL API docs, let's go ahead an make some queries.

Countries Query

When calling a REST API we need to specify an endpoint and a method (GET, POST, DELETE, PUT, PATCH). What operation the backend will try to perform is based on the on the combination of these two.

When consuming a GraphQL service, we have 3 types of operations:

  • query – a read‐only fetch, we are only getting data
  • mutation – a write followed by a fetch, so a mutation will be changing the data in some way, be it create update or delete, and in case return us some data back
  • subscription – a long‐lived request that fetches data in response to source events.

For this project, as first step, we want to get a list of countries. This would be a query operation. Whenever you write your operation you may add an optional name, for now we can omit it, since we have only one operation in your GraphiQL environment.

If you have no idea what to type, you can use CTRL + Space to get suggestions as what is possible to write in the editor.

You can open curly brackets (we are omiting the type of the operation and the name here) and start writing what we want to query - this is our selection set. Since all three operations always return some data, we always need to define which fields we want back. We start from the top most level, same way like clicking through the types in the Docs tab. The playground editor will keep auto-suggesting possible fields to select. Note there is no way to select all fields at once, as the idea is that you want to select only the ones you will actually need.

Now if I just add { countries } I will get an error after trying to run the query.

Missing selection set example

GraphQL provides awesome error messages, just follow them. In this case I am trying to query a type that is not scalar, that means it has fields of its own, so I need to specify which ones of these fields I want to get back. I add another set of curlies and add the name field. Since it is a String, that is a scalar type, I get no more complaints and receive an array of countries with only their names.

Querying countries names

Note the white space and indentation are both insignificant, we can type one line, or hit enter after each field we want to select, or even add commas. You can always make use of the Prettify button to make your query more readable.

Send the request

So far so good, now I want to make the same query from JavaScript. I will use fetch for this task. GraphQL services are not organized around endpoints but types - so we will not have an endpoint /countries that we need to call with a GET request for example. Instead we have one base URL https://countries.trevorblades.com/ and we will be making all our calls against it.

With GET

We can make our call with a simple GET request. What we need to do though is to pass along our query. And there is one way to do this with a GET request - query parameters. I will copy paste our query as is from the playground and assign it to a variable and since I like to intend my query for readability, I will use backticks:

const countriesQuery = `{
    countries {
        name
    }
  }`;

Now to make the query we need to pass a query parameter named query and set its value to the countriesQuery variable:

  async function () {
    const data = await fetch(`${baseURL}/?query=${countriesQuery}`, {
      headers: {
        'Content-Type': 'application/json',
      },
      method: 'GET',
    }).then(res => res.json());
  }()

With POST

Query params works just fine, but it might get hairy quite quicky, at least for my taste. So let's see if we could use the body of a POST request. We need to pass the query in the body like so:

  async function () {
    const data = await fetch(baseURL, {
      headers: {
        'Content-Type': 'application/json'
      },
      method: 'POST',
      body: JSON.stringify({
        query: countriesQuery,
      })
    }).then((res) => res.json());
  }()

Our body consists of an object that has one property named query and its value is the GraphQL query saved in the countriesQuery variable.

I would like to diplay the country flag in addition, so I will add it to my query:

const countriesQuery = `{
    countries {
        name
        emoji
    }
  }`;

Now that we have the data we need, we can go ahead and build our UI:

Rendred list of countries

I have used template strings for templaing and vanilla JavaScript to render the list of countries.

Country details query

Now I would like whenever I click on an item, to see further details about this country. Looking at the available types to query - there is a Country type.

Country type

If I try to query it as I did countries though, I will get the following error, saying that the field "country" requires an argument of type ID and none was provided:

Querying country error

This should not come as a surprise - in a REST API we used to pass along an id to get a specific resource from an endpoint, for example like this one /country/:id. How do we pass an argument to a GraphQL query? In the API documentation there is an example:

  country(
    code: ID!
  ): Country

So let's try it in the playground:

  {
    country(code: "DE") {
      name
    }
  }

We are now getting one item of type country with only one field: name.

Country details query with variables

Great, now since I will be making this request depending on which country the user is interacting with, I need to make the code argument value dynamic, instead of hard-coding it.

Until now we were performing operation without explicitly specifying the name of type of the operation. We will add it now:

  query {
    country(code: "DE") {
      name
    }
  }

The query is a GraphQL type itself and can receive arguments. Order does no matter, as each argument is named. The name must start with a $, so let's name our argument $countryCode. Since GraphQL is strictly typed we would need to specify the type of the argument, and we could copy what is written in the documentation but change to the name we are using: $countryCode: ID!. Last we can use the argument we defined in our query in place of the hard-coded string:

  query($countryCode: code: ID!) {
    country(code: $countryCode) {
      name
    }
  }

How to test this in the playground? Open the variables tab from the bottom left, and pass a JSON object with the variable name as its key, and as value the code of the country we want to fetch {"countryCode": "DE"}.

Query country with variables in playground

Making the request for country details

Alright, now we need to transfer the query from the playground into the fetch. Let's save our query in a variable, along with the selection set we want to get back:

const countryByCodeQuery = `
    query getCountry($code: ID!) {
      country(code: $code) {
        name
        continent {
          name
        }
        capital
        currency
        languages {
          name
        }
        states {
          name
        }
      }
    }
  `;

With a GET request

Let's start by using a GET request. We would need to pass two query parameters this time: query and variables. We need to stringify the variables object before passing it in the query params:

function getCountryDetails(code) {
  const variables = JSON.stringify({
    code,
  });

  return fetch(
    `https://countries.trevorblades.com/?query=${countryByCodeQuery}&variables=${variables}`,
    {
      headers: {
        "Content-Type": "application/json",
      },
      method: "GET",
    }
  ).then((res) => res.json());
}

With a POST request

I much prefer going with a POST request, as you pass nicely both the query and the variables in the body of the request:

function getCountryDetails(code) {
  return fetch("https://countries.trevorblades.com/", {
    headers: {
      "Content-Type": "application/json",
    },
    method: "POST",
    body: JSON.stringify({
      query: countryByCodeQuery,
      variables: {
        // same as code: code,
        code,
      },
    }),
  }).then((res) => res.json());
}

Note, since both the property and the variable I use as value have the same name, I can use object shorthand notation: variables: { code }.

Now on click we can display the results in our UI:

Countries list end result example

Last touches

Whenever the user is trying to interact with the collapsible list items, you can check if the data is already fetched, and make the request only if data isn't there. Many ways to do this, I have added a check if the section holding the content has any children.

Conclusion

Fetching data from a GraphQL service is not that different that fetching data from a REST API. What we gained is getting only the data we needed for the initial load, and we fetched incrementally further details upon request from the user. First load of details mihgt be a bit slower, but every next one will be fast, since we save the data and do not request the same piece of information twice.

There is certainly some learning to be done. The interactive playground is a nice tool that can be used to explore the available types a GraphQL service offers, and to test your queries before writing a single line of JavaScript. The error messages returned are quite helpful for debugging and getting things right.

Resources