TIL: How to use GraphQL variables to give my queries type safety
Tags: graphql, javascript, webdev, security
Published: 2021-10-25 09:19:24

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.

```text
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.

```graphql
{
  blogPostCollection(where: {slug: "what-is-a-rest-api"}) {
    items {
      slug
      title
      excerpt
      readingTime
    }
  }
}
```

And here’s the data we get back.

![A screenshot of the GraphiQL explorer showing a query made using a string in the where clause, and the data successfully returned.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0h7mlsl14xr3txfeo6rf.png)

Here’s how we can make the same request using JavaScript fetch (and no external dependencies!).

```javascript
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](https://whitep4nth3r.com/).

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!

## Use GraphQL variables for type safety and self-documenting queries

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:

1. Create what looks like a traditional function prefaced by the word `query`. You can name this query function whatever you like. I’ve named mine `GetBlogPostBySlug`.
1. 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.
1. 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!).

![A screenshot of the GraphiQL explorer showing the slug variable used correctly, passed in via a separate query variables tab, and the data returned is the same as the previous query.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ecws037v3bucgjhp6z4o.png)
 

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.

```javascript
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! 

## Going further with more types

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:

![A screenshot of the GraphiQL explorer, showing the docs button a the top right highlighted.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ovkgn1qyi9ub9qec6bx5.png)
 
 
Click on Query:

![A screenshot of the GraphiQL explorer, showing the docs tab open and the root types — query area highlighted.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mxls5x2bctlvecd7q2z4.png)
 

And find the content type you’d like to inspect.

![A screenshot of the GraphiQL explorer, showing the query tab open to inspect the scheme, with the type order: EventOrder highlighted.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b9jq01f56dmga7s6oy6y.png)
 

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:

```javascript
// 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](https://graphql.contentful.com/) without worrying. You can [check out the code on GitHub](https://github.com/whitep4nth3r/p4nth3rblog/tree/main/contentful) 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](https://www.contentful.com/developers/videos/learn-graphql/) in our developer portal. Happy querying, friends!