The Fetch API is a core primitive part of today's web standards that replaces XMLHttpRequest
.
It adds support for promises and aims to provide a consistent API with Request
and Response
objects that make working with fetch the same experience, no matter the runtime.
If you've made a request on the server or client over the last few years you no doubt have installed packages like axois
that handle making a request.
This library and dozens more abstracted the complexity of handling XMLHttpRequest
and built a simpler API that developers could use to make requests, handle promises, and avoid callback hell.
The DX these libraries brought to developers is now thankfully part of the native web API.
fetch('YOUR_API_URL')
.then(res => res.json())
.then(data => console.log(data))
.error(err => console.log(err))
One of the biggest benefits of using Fetch is that it's already part of your runtime. If you're using Deno, Node, the browser or something else, you can use the same API to make a GraphQL request.
There's no need to install any dependencies or configure clients if you don't need to leverage any client-side caching — which is becoming more and more redundant with modern web frameworks that render on the server and handle caching for you.
We will create a Grafbase backend and run it locally using the CLI.
In the root of your project run the following command:
npx grafbase init
You can now start the GraphQL API (and example schema) using:
npx grafbase dev
A GraphQL API (like Grafbase) expects the request to contain some details to help execute the operation.
- URL
- Headers
- Body
The URL is a required argument for fetch
. We will provide a second argument that specifies that:
- The HTTP method as
POST
- The
content-type
isapplication/json
- Body is stringified
JSON
const query = `
query GetTodos {
todoCollection(first: 100) {
edges {
node {
id
}
}
}
}
`
fetch('http://localhost:4000/graphql', {
method: 'POST',
headers: {
'content-type': 'application/json',
// Other headers, including `authorization` or `x-api-key`
},
body: JSON.stringify({
query,
}),
})
To pass variables with the GraphQL request using the Fetch API we can update body
to include the variables
object:
const query = `
query GetTodos($first: Int) {
todoCollection(first: $first) {
edges {
node {
id
}
}
}
}
`
fetch('http://localhost:4000/graphql', {
method: 'POST',
headers: {
'content-type': 'application/json',
// Other headers, including `authorization` or `x-api-key`
},
body: JSON.stringify({
query,
variables: {
first: 100,
},
}),
})
Calling fetch
returns a Promise. This means you will need to await
or chain callback functions to handle the response or errors.
fetch('http://localhost:4000/graphql', {
method: 'POST',
headers: {
'content-type': 'application/json',
// ...
},
body: JSON.stringify({
query,
variables: {
first: 100,
},
}),
})
.then(res => res.json())
.then(data => console.log(data))
.error(err => console.log(err))
Using the exact same code above you change the contents of the query
variable to include a GraphQL mutation:
const query = `
mutation CreateNewTodo($title: String!) {
todoCreate(input: {
title: $title
}) {
todo {
id
}
}
}
`
Typically inside of the first callback you would check if the response is ok
. If it is you would then call res.json()
and do what you need with the results...
Since GraphQL errors are returned in the JSON response, we will need to get those from the response and log them accordingly.
We'll update the request to destructure data
and errors
from the Promise res.json()
returns:
fetch('http://localhost:4000/graphql', {
method: 'POST',
headers: {
'content-type': 'application/json',
// ...
},
body: JSON.stringify({
query,
variables: {
first: 100,
},
}),
})
.then(res => res.json())
.then(({ data, errors }) => console.log({ data, errors }))
.error(err => console.log(err))
If you now execute an invalid query you will get a message
inside of the errors
array.
Libraries like axios
and got
still provide a wide range of features that you would have to manually implement using the Fetch API. If you have to deal with retrying network requests, refreshing tokens, or intercept requests, the native Fetch API might not be for you.
Handling errors is also something you will need to consider when working with the Fetch API.
If you're going to be using GraphQL subscriptions or live queries with something like EventSource
you will need to implement some extra logic with these requests to correctly handle the content type and responses.