Jack Franklin

Getting started with GraphQL: what client to use?

When I first started working with GraphQL APIs my first challenge was to decide what GraphQL frontend library I wanted to use. I can remember spending all morning exploring all sorts of options, from small libraries like graphql-request to slightly larger ones like urql and finally the most well known like Apollo. These are all great libraries - in fact we use urql at work - but at this point in time I was working with a tiny GraphQL library that I'd built for a side project and I really didn't need any complexity. I think I lost a good couple of hours trying to decide before thinking: what if I made my own?

This post is not meant to criticise libraries: they provide a bunch of features that many applications will want and need, but if you're just getting started, they might be overkill for your needs.

Do you need a library to use GraphQL?

I had in my head this mindset that making a request to a GraphQL API was "special" and not something that I could do with the fetch API, for example. I'm not really sure where this came from but I think I'd seen so many talks about Apollo and various client libraries doing all sorts of smart things I'd ended up assuming that I'd use one of those. But Apollo packs in a vast array of features that I really didn't need on my side project. I wanted to make a request and get the data. Concerns such as smart caching and cache invalidation were not present for me.

When you're starting to learn something it can be tempting to reach for libraries to fill in gaps in knowledge but I highly recommend trying to avoid doing this when possible. I'm very happy that I made the decision to write my own tiny client because it plugged gaps in my knowledge and de-mystified how a GraphQL API works. In this post I'll talk through how to get started talking to a GraphQL API just by using the fetch API and nothing more.

A sample GraphQL API

We need a sample API for this and I've made one that lives on Heroku: http:faker-graphql-api.herokuapp.com/graphql. This API returns some fake people (all data is generated by Faker.js). It lets us query for people and get their names:

{
people {
name
}
}

Returns an array of ten people and their names. This is the query we're going to use as our example today.

My dummy API is hosted on a free Heroku instance so please be patient if it takes some time to boot up when you request it.

Making a request to a GraphQL API

It turns out there are some simple steps to follow to talk to a GraphQL endpoint:

As long as we follow those rules we can easily use fetch to talk to the API. Let's do it!

const api = 'http:faker-graphql-api.herokuapp.com/graphql'

export const request = ({ query }) => {
return fetch(api, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query,
}),
})
.then(response => response.json())
.then(result => {
console.log('got here!', result)
return result
})
}

The request function takes an object and expects the query key to contain the raw GraphQL query. The fetch API takes the URL and an object of options, which are used to configure the request: we set method: 'POST' and the Content-Type header as discussed and then use JSON.stringify({ query }) to generate the body for the request, passing in the query that was passed in to our request function. Finally, the GraphQL API will return JSON so we parse the response before returning it (I've logged it just to aid debugging but feel free to skip that!).

With that we can make our request:

request({
query: `{ people { name } }`,
})

And you should get some people back! ๐ŸŽ‰.

If you only need to make basic requests in your app you could stop here and be done. We've saved having to install, learn and ship in our bundle any additional libraries. Of course this comes with less functionality - but for some projects that might be just fine.

If you do need caching and more advanced features I'd highly recommend a well tested, established library rather than rolling your own!

Supporting variables

Another feature of GraphQL is that queries can take variables. For example, the fake API lets us find a single person by their ID:

query fetchPerson($id: Int!) {
person(id: $id) {
name
}
}

To support this our API needs to pass variables through as well that it includes in the request:

export const request = ({ variables, query }) => {
return fetch(api, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query,
variables,
}),
})
.then(response => response.json())
.then(result => {
console.log('got here!', result)
return result
})
}

And now our client supports variables:

request({
query: `query fetchPerson($id: Int!) {
person(id: $id) {
name,
}
}
`
,
variables: {
id: 1,
},
})

If this is all you need, or you're not using React for your frontend, you can stop here. This client will be plenty good enough to keep you going as you work with and get more familiar with GraphQL. By working with your own implementations first you'll find you have a greater fundamental understanding when swapping to a library, and you'll understand the features the library provides better.

A React hook!

Finally let's see how easy it would be to wrap this up in a React hook for those of you working with React.

If you're not familiar with hooks, I wrote an introduction to them which will help get you up to speed.

Creating the hook is a case of wrapping our request function in a React.useEffect hook and storing the response via React.useState:

export const useGraphQL = ({ variables, query }) => {
const [data, setData] = React.useState(null)

React.useEffect(
() => {
request({ variables, query }).then(setData)
},
[variables, query]
)

return [data]
}

This hook is missing some useful features like tracking if we're loading or not, but I'll leave that as an exercise to the reader ๐Ÿ˜ƒ

We can use this hook within a component like so:

const [data] = useGraphQL({
query: `{ people { name } }`,
})

And it works! There is one gotcha though that I want to highlight. If you do this:

const [data] = useGraphQL({
variables: {},
query: `{ people { name } }`,
})

You'll cause an infinite loop of requests, which isn't what we want! This is because React.useEffect has variables as a dependency and every time it changes it will cause the effect to re-run. Every re-render this code runs and variables: {} creates a new object every time which means React.useEffect will re-run.

We can fix this by remembering to wrap our variables in a React.useMemo hook to ensure that we only recalculate the variables if we need to:

const vars = React.useMemo(
() => {
return {
id: props.id,
}
},
[props.id]
)

const [data] = useGraphQL({
variables: vars,
query: `{ people { name } }`,
})

But this requires you to remember to do this every time. Instead what we can do is convert the variables within our useGraphQL hook to a string, via JSON.stringify, and use that as the dependency to useEffect:

const stringifiedVars = JSON.stringify(variables)
React.useEffect(
() => {
request({ variables, query }).then(setData)
},
[stringifiedVars, query]
)

โ—๏ธThis isn't the best solution but it is the easiest and will serve just fine for most projects. It's also similar to how the popular urql works although that uses the fast-json-stable-stringify to avoid some of the performance problems with JSON.stringify.

Conclusion

Although this post has focused on GraphQL I hope that your main takeaway is to resist diving straight for libraries. You can often get a long way with a few lines of code you write yourself, particularly when learning a new technology. This will help your understanding of the tech that you're learning but also your understanding of libraries: if you've written a library yourself, however small and straight forward, you're more likely to be able to follow how the more complex libraries work.