Build a Bare-Bones GraphQL Client w/ Fetch
Tags: graphql, webdev, javascript, beginners
Published: 2019-07-20 15:32:36

There are already plenty of great browser based GraphQL clients. Check out, [Exploring GraphQL Clients](https://medium.com/open-graphql/exploring-different-graphql-clients-d1bc69de305f), for a solid rundown of the existing options.

So why would you write your own GraphQL Client? 
- Maybe it's for a tiny thing and you don't want to deal with pulling in NPM packages and bundling code.
- Or you're writing a [Chrome Snippet](https://developers.google.com/web/tools/chrome-devtools/javascript/snippets) 
- Maybe, like me, you just want to **go through the motions to learn more about GraphQL**.

> The purpose of this post isn't to create a competing/better GraphQL client. The 10 minutes it takes to craft our own could never compete with the features and robustness of the existing libraries.

## The Basics

At minimum, a client-side request to a GraphQL endpoint needs to be a `POST`, with a `query` property in the Request body.  The GraphQL server will respond with JSON in the form of either `{ data }` or `{ errors }`.

### GraphQL Request
- HTTP `POST`
- POST body contains a `query` property
- `query` is a valid GraphQL string

### GraphQL Response 
- JSON in the shape of `{ data, errors }`
- `data`, if success, will be an object that contains a property for each of the things you asked for in your GraphQL query.
- `errors`, if there are any, will be an array of objects. Each error object should at least have a `message` property.

## An example request
In this example we connect to a publicly available [Pokemon GraphQL API](https://graphql-pokemon.now.sh) and `console.log` the result of a query.



```javascript
// Public Pokement API
const ENDPOINT = "https://graphql-pokemon.now.sh";
// Use a tool like GraphiQL to help you craft your query
const POKEMON_QUERY = `
  query TopFivePokemon {
    pokemons(first:5) {
      name
      image
    }
  }
`;

// Call fetch, first passing the GraphQL endpoint, then the Request Options
fetch(ENDPOINT, {
  method: "POST",
  headers: {
    "Content-Type": "application/json"
  },
  // The request body should be a JSON string
  body: JSON.stringify({ query: POKEMON_QUERY })
})
  // Handle a JSON response
  .then(res => res.json())
  .then(result => console.log(result));

```
In the above snippet we:
- Use JavaScript string templates to make the GraphQL query readable
- Tell the server we are sending JSON with the `Content-Type` header
- Set our POST `body` to be the JSON string of our `{ query }` object.
- Expect the server to return JSON, and handle the response accordingly with `res.json()`


*Console Output*
```
{
  "data": {
    "pokemons": [
      {
        "name": "Bulbasaur",
        "image": "https://img.pokemondb.net/artwork/bulbasaur.jpg"
      },
      {
        "name": "Ivysaur",
        "image": "https://img.pokemondb.net/artwork/ivysaur.jpg"
      },
      {
        "name": "Venusaur",
        "image": "https://img.pokemondb.net/artwork/venusaur.jpg"
      },
      {
        "name": "Charmander",
        "image": "https://img.pokemondb.net/artwork/charmander.jpg"
      },
      {
        "name": "Charmeleon",
        "image": "https://img.pokemondb.net/artwork/charmeleon.jpg"
      }
    ]
  }
} 
```

### Basic Client Factory

The above snippet worked great, but let's enhance things slightly so that our GraphQL endpoint is configurable and we don't rely on some random `ENDPOINT` variable existing.

We want to **create an instance of a GraphQL client that is tied to a specific endpoint**. 

> The [Factory Pattern](https://medium.com/javascript-scene/javascript-factory-functions-with-es6-4d224591a8b1) is just a fancy way of saying, *"make a function that creates instances of things"*. It's an alternative to new'ing up a class, `createClient(endpoint)` instead of `new Client(endpoint)`
 
Let's shoot for an API that looks like this:
```javascript
// Create an instance of a GraphQL client by passing in your endpoint
let client = createClient("https://graphql-pokemon.now.sh");

// Make an actual GraphQL request by passing a GraphQL query 
// to the request method on the client
client.request(POKEMON_QUERY)
  .then(result => console.log(result));
```

To implement:
1. Take the `fetch` code from the first example and turn it into a function named `request`. 
2. Wrap `request` with another function, `createClient`, to create a closure around our `endpoint` parameter.


```javascript
function createClient(endpoint) {

  let request = function(query) {
    return fetch(endpoint, {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      // The request body should be a JSON string
      body: JSON.stringify({ query })
    }).then(res => res.json());
  };

  return { request };
};
```

### Client Factory with Custom Headers

Now that our endpoint is configurable lets add a little more flexibility by allowing developers to pass in HTTP `headers` that should be included on all requests (common for an `Authorization` header).

- We let our `createClient` function take in a second parameter for `headers` and default it to an empty object. 
- We spread the passed in `headers` onto the headers property of our Fetch options.

```javascript
// Allow an optional second param for request headers
function createClient(endpoint, headers = {}) {

  let request = function(query) {
    return fetch(endpoint, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        // spread the passed in headers
        ...headers
      },
      body: JSON.stringify({ query })
    }).then(res => res.json());
  };

  return { request };
}
```

### GraphQL Variables

The last thing we want to support are variables to tokenize our GraphQL queries (and mutations).  In the screenshot below you can see that the `name` is getting set via a variable.

![GraphQL Variable](https://thepracticaldev.s3.amazonaws.com/i/why1zn46l55qq7dnjfla.PNG)

This is actually pretty easy to support. All we need to do is add an additional property to our POST body named `variables`.  

However, instead of only adding support for just `variables`, let's have `request` take in an optional second param, generically named `queryOptions`. Then we'll spread whatever `queryOptions` are given (in our case, `variables`) onto the POST body.

```javascript
function createClient(endpoint, headers = {}) {

  // Take in an optional queryOptions object
  let request = function(query, queryOptions = {}) {
    return fetch(endpoint, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        ...headers
      },
      // spread the passed in queryOptions onto the POST body
      body: JSON.stringify({ query, ...queryOptions })
    }).then(res => res.json());
  };

  return { request };
}
```

And that's it. Our final client factory can support configuring custom headers as well as GraphQL variables.

Final Usage example:
```javascript
const POKEMON_BY_NAME_QUERY = `
query GetByName($name:String!) {
  pokemon(name:$name) {
    types 
    name
    image
    height {
      minimum
      maximum
    }
  }
}`;

let client = createClient("https://graphql-pokemon.now.sh", {
  Authorization: "Bearer ABC123"
});

async function getPokemonByName(name) {
  let {data,errors} = await client.request(POKEMON_BY_NAME_QUERY, { variables: { name } })
  if (errors) {
    console.log("Uh Oh!", errors.map(e => e.message));
  }
  return data.pokemon
}


getPokemonByName("Pikachu");
```