TIL: How to use GraphQL variables to give my queries type safety
One of the things I love about GraphQL is how straightforward it is to get up and running with little to no experience. Using a browser-based GraphiQL interface — such as the GraphiQL explorer provided by Contentful — you can inspect your schema right there in the browser, and construct your queries in no time. But how can you make sure your GraphQL queries are safe from nasties? Let’s find out.
To inspect your schema in Contentful’s GraphiQL interface and construct a GraphQL query, enter this URL in your browser, and swap out the SPACE_ID
for your Contentful space ID, and ACCESS_TOKEN
for your Contentful Delivery API Key.
https://graphql.contentful.com/content/v1/spaces/{SPACE_ID}/explore?access_token={ACCESS_TOKEN}
Make an HTTP POST request with your programming language of choice — and boom — you’ve got data.
This is an example of a query that we can use to request data from a single blog post by slug. Notice how we’re using a where
clause to filter the items by a slug
that matches a string we supply.
{
blogPostCollection(where: {slug: "what-is-a-rest-api"}) {
items {
slug
title
excerpt
readingTime
}
}
}
And here’s the data we get back.
Here’s how we can make the same request using JavaScript fetch (and no external dependencies!).
const query = `{
blogPostCollection(where: {slug: "what-is-a-rest-api"}) {
items {
slug
title
excerpt
readingTime
}
}
}`;
const response = await fetch(
`https://graphql.contentful.com/content/v1/spaces/${SPACE_ID}/environments/master`,
{
method: "POST",
headers: {
Authorization: `Bearer %ACCESS_TOKEN%`,
"content-type": "application/json",
},
body: JSON.stringify({ query }),
},
).then((response) => response.json());
console.log(response);
All of this is great, and perfectly valid GraphQL. And, if you’re using a static site generator such as Next.js, Gatsby or Nuxt which will pre-render your pages at build-time and serve static pages to the client, you should be good to go. I’d been doing this for months using Contentful’s GraphQL API to fetch my data to power my personal website built with Next.js.
However, whilst queries like this are super-fast to write and get your projects out fast — what if you’re making GraphQL queries dynamically on the client and not as part of a static site build? What if someone could play around with your data in real-time by inserting an incorrect data type, a GraphQL mutation or similar instead of a string?
Here’s where GraphQL variables save the day!
It’s worth mentioning that because the Contentful GraphQL API is read-only, this kind of scenario won’t happen — but security considerations are always good to bear in mind regardless. Let’s take a look!
GraphQL variables offer an extra layer of protection in your queries, namely type safety — meaning that a query will only accept dynamic variables of certain data types, such as String, Int (number), DateTime and so on. And what’s more, there isn’t much more work needed to make your GraphQL queries safer!
To use variables in your GraphQL queries:
Create what looks like a traditional function prefaced by the word
query
. You can name this query function whatever you like. I’ve named mineGetBlogPostBySlug
.Inside the parentheses of the function declaration, name and define the types of the variables the query will accept, and prefix your variable names with a
$
. The query below will accept a variable named$slug
, which is of type String. The bang or exclamation mark that comes after the type name means that it is a required variable for the query to execute.In an HTTP POST request to the GraphQL API, variables are passed to the GraphQL request as a separate property inside the body of the request. Click on the query variables pane at the bottom of the GraphiQL explorer. Create an object, and add your stringified variable name and value as “key”: “value” (it’s important to stringify the key name here!).
Let’s look at an example of using GraphQL variables using JavaScript fetch. Notice how we have replaced the original query with the function-style query from above, and have created a variable named variables
that we pass into the body of the HTTP request.
const query = `query GetBlogPostBySlug($slug: String!) {
blogPostCollection(where: {slug: $slug}) {
items {
slug
title
excerpt
readingTime
}
}
}`;
const variables = { slug: "what-is-a-rest-api" };
const response = await fetch(
`https://graphql.contentful.com/content/v1/spaces/${SPACE_ID}/environments/master`,
{
method: "POST",
headers: {
Authorization: `Bearer %ACCESS_TOKEN%`,
"content-type": "application/json",
},
body: JSON.stringify({ query, variables }),
},
).then((response) => response.json());
console.log(response);
And that’s how I learned to make my GraphQL queries type-safe and free from nasty attacks on dynamic API calls!
There are a variety of different variable data types available on Contentful’s GraphQL API. As well as the standard data types such as String, Int and DateTime, you can also pass variables to a query that are entry-specific and API-specific.
To inspect the types available on your schema, click on the Docs links at the top right of the GraphiQL explorer:
Click on Query:
And find the content type you’d like to inspect.
Another thing I learned on this journey is that you can’t use variables in GraphQL for everything — namely keys in WHERE clauses.
I recently created a GraphQL query to fetch the events on my website. On the main events page, I wanted to show future events in ascending order, and on the past events page, events in descending order.
The two supported variables involved in this query are:
$order
— date_ASC or date_DESC$date
— as an ISO string
But I also needed a third dynamic variable — which was to control whether the API returned events before (date_lt
— date less than) or after (date_gt
— date greater than) a particular date. Unfortunately, this part of a GraphQL query cannot be controlled with a variable, and so I had to be creative and pass in a calculated string to the query like so:
// https://github.com/whitep4nth3r/p4nth3rblog/blob/main/contentful/Events.js
import ContentfulApi from "@contentful/Api";
const defaultOptions = {
future: true,
};
/*
* Get all events -- future by default
*/
static async getEvents(options = defaultOptions) {
// Calculate date_ASC for future events, or date_DESC for past events
const order = options.future ? "date_ASC" : "date_DESC";
// Generate today's date
const date = new Date();
// And format it to an ISO String
const formattedDate = date.toISOString();
// Decide on the date filter to pass in as a string
const dateFilter = options.future ? "date_gt" : "date_lt";
// Construct variables object to send with the HTTP POST request
const variables = { date: formattedDate, order };
// Build the query
const query = `query GetEvents($date: DateTime!, $order: [EventOrder]!) {
eventCollection(where: {${dateFilter}: $date}, order: $order) {
items {
sys {
id
}
date
name
link
description
timeTbc
isVirtual
image {
url
description
height
width
}
}
}
}`;
// Call out to the base API call
const response = await this.callContentful(query, variables);
const eventCollection = response.data.eventCollection.items
? response.data.eventCollection.items
: [];
return eventCollection;
}
One other thing to notice is that the $order
variable is of type EventOrder, which we saw when we inspected the schema above, which is an API and entry-specific type!
So there you have it. Fancy and safe GraphQL queries, so you can build great stuff with the Contentful GraphQL API without worrying. You can check out the code on GitHub for the full range of queries I make with GraphQL on my website, and if you’re curious about GraphQL and want to learn more, you can learn along with Stefan Judis’ React and GraphQL video course in our developer portal. Happy querying, friends!