Published on

Consuming GraphQL in Plain JavaScript

Authors

Many APIs have moved towards supporting GraphQL in addition to REST or even supporting it exclusively. However, if you need to consume a GraphQL API, you wouldn't be blamed for thinking you need to use React and/or a bunch of additional libraries just to get it to work. That's because many tutorials and sample code seem to work off the assumption that, if you're working with GraphQL, you are working these libraries.

However, a query to a GraphQL API is just a properly formatted HTTP request. A GraphQL response is just JSON. You don't need any fancy libraries to handle either of these. In this tutorial, I want to take a different approach and show you how easy it is to call a GraphQL API from both Node and client-side JavaScript without libraries.

The Basics of a GraphQL Request

Unlike RESTful APIs, GraphQL has a single endpoint. A typical GraphQL HTTP request is sent as a POST request, though GraphQL can respond to GET requests.

There are three pieces of data that can be sent with your GraphQL request: query, operationName and variables.

  • query is required and contains (you guessed it) the GraphQL query. Since GraphQL functions via a single endpoint, the data that the endpoint responds with is entirely dependent upon your query. The query needs to be properly formatted GraphQL. Curious how to construct your queries? Check out my tutorial on how to write GraphQL queries.

  • variables is optional and is a JSON object containing the value of any variables being passed to your query. For instance, if your query requires a variable of id (which will appear in the query as $id) then you'd need to send variables as the following:

    {
    "id":1
    }
  • operationName is also optional. It is used to specify which operation to run in the case where you have a query containing multiple named operations.

If you send a GraphQL as a GET request, you'll need to pass the above as query parameters. Given that GraphQL queries can get long, this really isn't optimal, so we'll stick to POST requests. In this tutorial, we'll be hitting a simple Scooby Doo API that I created on StepZen to connect to a MySQL data source (StepZen is in private alpha right now, but you can request access here).

Sending Queries in Node.js

We can send queries via Node.js without any special libraries, leveraging the standard Node https library to form a POST request. Let's look at a simple example using no special libraries (note that I do use dotenv to pull in the API key for accessing my StepZen backend). In this example, I am only passing a query, which needs to be stringified before sending. Other than that, this is a fairly standard HTTP POST.

const https = require('https');
require('dotenv').config();

const data = JSON.stringify({
  query: `{
    characters(isMonster:true) {
      name
      episode {
        name
      }
    }
  }`,
});

const options = {
  hostname: 'biggs.stepzen.net',
  path: '/scoobydoo/scoobydoo/__graphql',
  port: 443,
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Content-Length': data.length,
    Authorization: 'Apikey ' + process.env.STEPZEN_API_KEY,
    'User-Agent': 'Node',
  },
};

const req = https.request(options, (res) => {
  let data = '';
  console.log(`statusCode: ${res.statusCode}`);

  res.on('data', (d) => {
    data += d;
  });
  res.on('end', () => {
    console.log(JSON.parse(data).data);
  });
});

req.on('error', (error) => {
  console.error(error);
});

req.write(data);
req.end();

Again, the data returned is just JSON, so if we were to run this, the output in the console would be:

{
  characters: [
    { episode: [Object], name: 'Black Knight' },
    { episode: [Object], name: 'Ghost of Captain Cutler' },
    { episode: [Object], name: 'Phantom' },
    { episode: [Object], name: 'Miner Forty-Niner' }
  ]
}

Simplifying the Request

Let's make this a little simpler by using something like node-fetch to reduce the amount of boilerplate code necessary to make the HTTP request. The node-fetch library implements the JavaScript fetch API from the browser in Node. This allows us to drop around 11 lines of code (a reduction of 25%), while also being much easier to read.

const fetch = require('node-fetch');
require('dotenv').config();

async function getData() {
  const data = JSON.stringify({
    query: `{
        characters(isMonster:true) {
          name
          episode {
            name
          }
        }
      }`,
  });

  const response = await fetch(
    'https://biggs.stepzen.net/scoobydoo/scoobydoo/__graphql',
    {
      method: 'post',
      body: data,
      headers: {
        'Content-Type': 'application/json',
        'Content-Length': data.length,
        Authorization: 'Apikey ' + process.env.STEPZEN_API_KEY,
        'User-Agent': 'Node',
      },
    }
  );

  const json = await response.json();
  console.log(json.data);
}

getData();

The result of running the above would be identical to our prior example.

Passing Variables

In this example, our query has a variable that needs to be passed ($id). In order to pass the variable, we need add a variables value to the data contained in the request body. This should be a JSON formatted list wherein each variable required by the query has a corresponding value in the JSON.

const fetch = require('node-fetch');
require('dotenv').config();

async function getData(id) {
  const data = JSON.stringify({
    query: `query ScoobyDooWhereAreYou($id: ID!)  {
        character(id:$id) {
          name
          isMonster
        }
      }`,
    variables: `{
        "id": "${id}"
      }`,
  });

  const response = await fetch(
    'https://biggs.stepzen.net/scoobydoo/scoobydoo/__graphql',
    {
      method: 'post',
      body: data,
      headers: {
        'Content-Type': 'application/json',
        'Content-Length': data.length,
        Authorization: 'Apikey ' + process.env.STEPZEN_API_KEY,
        'User-Agent': 'Node',
      },
    }
  );

  const json = await response.json();
  console.log(json.data);
}

getData(1);

In this case, I am passing the ID value of 1, which, not coincidentally, returns Scooby Doo:

{ character: { isMonster: false, name: 'Scooby Doo' } }

Now we know where Scooby Doo is.

Sending Queries in Client-side JavaScript

Calling GraphQL queries via client-side JavaScript is nearly identical to the fetch example above with a couple of small differences. First, I obviously do not need to import a library to support fetch. Second, and more importantly, I do not have access to environment variables. It's worth emphasizing that, if your API requires passing some sort of API key or credentials, you will not want to perform this client side as your credentials will be exposed. A better solution would be to call a serverless function that has access to these credentials and then calls the API for you, returning the result. If your serverless function is written in JavaScript, the Node code from the prior examples would work. However, in the case that the API is wide open, let's look at how this is done (note that my example does have an API key, but please do as I say and not as I do...at least, in demos).

The following example calls my Scooby API to get a list of monsters and the episodes that they were featured in (sorry Scooby fans, I only have a handful of monsters from season 1 populated just yet). It then takes the results and displays them in the browser. While not important to the GraphQL call, I use js-beautify to properly format the JSON result to display and then Prism to color it.

<html>
  <head>
    <title>GraphQL Query Example</title>
    <link href="css/prism.css" rel="stylesheet" />
  </head>
  <body>
    <pre><code class="language-json" id="code"></code></pre>
    <script src="js/prism.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/js-beautify/1.13.0/beautify.js"></script>
    <script>
      (async function () {
        const data = JSON.stringify({
          query: `{
    characters(isMonster:true) {
      name
      episode {
        name
      }
    }
  }`,
        });

        const response = await fetch(
          'https://biggs.stepzen.net/scoobydoo/scoobydoo/__graphql',
          {
            method: 'post',
            body: data,
            headers: {
              'Content-Type': 'application/json',
              'Content-Length': data.length,
              Authorization:
                'Apikey DONOTSENDAPIKEYS',
            },
          }
        );

        const json = await response.json();
        document.getElementById('code').innerHTML = js_beautify(
          JSON.stringify(json.data)
        );
        Prism.highlightAll();
      })();
    </script>
  </body>
</html>

The result of running this code is the JSON response containing the characters and episode data displayed in the browser.

browser output

Obviously, you won't typically want to simply display the result of a query to a user, so let's look at how you would use the data returned.

Consuming GraphQL Query Responses

One of the great things about GraphQL is that the response is just plain JSON, so consuming the data is easy. The nicer part of this is that the response mirrors the query, meaning that you don't need to spend a lot of time parsing through documentation about the response. So, let's quickly take the example above and utilize the returned data rather than simply displaying it.

The code below takes the JSON response and then transforms it into HTML (using template literals) to append the items to an HTML list.

<ul id="monsterList"></ul>
<script>
  (async function () {
    const data = JSON.stringify({
      query: `{
    characters(isMonster:true) {
      name
      episode {
        name
      }
    }
  }`,
    });

    const response = await fetch(
      'https://biggs.stepzen.net/scoobydoo/scoobydoo/__graphql',
      {
        method: 'post',
        body: data,
        headers: {
          'Content-Type': 'application/json',
          'Content-Length': data.length,
          Authorization:
            'Apikey DONOTSENDAPIKEYS',
        },
      }
    );

    const characterData = await response.json();
    const templateFn = (name, episode) => `<li>${name} (${episode})</li>`;
    const monsterList = document.getElementById('monsterList');
    characterData.data.characters.map((character) => {
      monsterList.insertAdjacentHTML(
        'beforeend',
        templateFn(character.name, character.episode.name)
      );
    });
  })();
</script>

The output of running this simple example is an unordered list of characters with the episode they appeared in.

HTML output

Where To Go From Here

The goal here is not to dissuade anyone from using a GraphQL client library for performing GraphQL queries. They offer far more capabilities than the simple ones I've discussed here. In addition, many have features designed to make it easy to integrate with a frontend framework (like React, Vue, Angular). However, it's important for anyone exploring GraphQL, especially when comparing it to REST, that it's clear that consuming GraphQL does not require any external dependencies. If you're interested in exploring some of the JavaScript libraries, here are a few of the popular ones: