oli's profile picture
Article2.6 minute read

Minimum viable GraphQL: the client

GraphQL tutorials often assume you'll be using a client-side library like Apollo or Urql to manage your data-fetching. This can make it hard to separate what's GraphQL itself from stuff implemented by the library.

I'm going to demonstrate how to make basic GraphQL queries directly from the browser using vanilla JavaScript. If you know how to make POST requests using fetch then you're 90% of the way there already.

If you've never encountered GraphQL before or you're confused by any of the terminology take a look at my previous post on GraphQL concepts.

The query

GraphQL queries can be represented as strings in JavaScript. Template literals (backticks) are handy for writing multiline strings. For example:

const myQuery = `
{
  allPokemon {
    name
  }
}
`;

The request

We'll be sending our query as a POST request, with a JSON body. The GraphQL spec also allows GET requests using a URL-encoded query, but sending JSON is usually easier.

The body will be an object with a query property containing (surprisingly) our query.

A basic query

We need to make a fetch call, setting the method to POST and passing a JSON body containing our query:

const query = `
{
  allPokemon {
    name
  }
}
`;

fetch("https://pokemon-gql.now.sh/api", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({ query }),
});

We can then do the standard fetch promise/error handling to see the result of our request:

const query = `
{
  allPokemon {
    name
  }
}
`;

fetch("https://pokemon-gql.now.sh/api", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({ query }),
})
  .then((response) => {
    if (!response.ok) throw new Error("Request failed");
    return response.json();
  })
  .then((json) => {
    console.log(json);
  })
  .catch((error) => {
    console.error(error);
  });

If successful this should log:

{
  "data": {
    "allPokemon": [
      { "name": "bulbasaur" },
      { "name": "ivysaur" },
      { "name": "venusaur" },
      { "name": "charmander" },
      { "name": "charmeleon" },
      { "name": "charizard" },
      ...
    ]
  }
}

A query with variables

We can update our query to take an argument (see this section of the previous post for more details).

const query = `
query Pikachu($name: String!) {
  pokemon(name: $name) {
    id
    name
    weight
  }
}
`;

We need to add a variables key to our body object to pass in dynamic values. The query itself should always be a static string (like SQL queries).

variables should be an object containing all the arguments your query requires. In this case we only need one: the name.

const query = `...`;

const variables = { name: "pikachu" };

fetch("https://pokemon-gql.now.sh/api", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({ query, variables }),
})
  // ... standard promise stuff
  .then(console.log);

If successful this should log:

{
  "data": {
    "pokemon": {
      "id": "25",
      "name": "pikachu",
      "weight": 60
    }
  }
}

Dynamic variables

If we want our variables to be dynamic (maybe taken from user input) we can create a reusable fetching function that takes the variables as arguments.

const query = `...`;

function fetchPokemon(name) {
  return fetch("https://pokemon-gql.now.sh/api", {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({ query, variables: { name } }),
  }).then((response) => {
    if (!response.ok) throw new Error("Request failed");
    return response.json();
  });
}

fetchPokemon("pikachu").then(console.log).catch(console.error);

We could then call fetchPokemon inside an event handler where we have a user's chosen name.

A complete example

Here's a small demo app that let's you search for Pokémon by name.