The Blog

 

Extracting usable data from various API endpoints can be tricky. I faced this problem recently when I wanted to write a script to gather information about some users and repositories on GitHub. In the past, I’d used GitHub’s RESTful API, and pieced my data together from a slew of endpoints.

Even though my knowledge of GraphQL was limited, I decided to take the learning opportunity and try GitHub’s GraphQL API.

In this blog post, I describe how I got started configuring my development environment for GraphQL syntax validation, schema validation, and highlighting.

My first step was to fire some test queries at the API. I quickly found that GraphiQL is an indispensable tool to do this. Importantly, GraphiQL consumes GraphQL’s introspection system to provide inline documentation and validation, which means you don’t have to keep switching contexts back to your web browser to view the API documentation. It’s great!

I got my queries working in GraphiQL, and began to implement the script.

A purpose-built client

To make my queries, I decided to use @octokit/graphql, which provides a very thin client aimed at GitHub. @octokit/graphql does not provide an object-oriented API; there are no classes to instantiate or methods to call. The module exports a single async function which accepts a query, query parameters, and headers (including an auth token).

Here’s an example, lifted from the documentation:

const graphql = require('@octokit/graphql');
const {repository} = await graphql(`{
  repository(owner:"octokit", name:"graphql.js") {
    issues(last:3) {
      edges {
        node {
          title
        }
      }
    }
  }
}`, {
  headers: {
    authorization: `token secret123`
  }
});

Note: Mind the top-level await there.

The contents of repository (as of this writing) looked like this:

{
  "issues": {
    "edges": [
      {
        "node": {
          "title": "An in-range update of webpack is breaking the build 🚨"
        }
      },
      {
        "node": {
          "title": "An in-range update of webpack-bundle-analyzer is breaking the build 🚨"
        }
      },
      {
        "node": {
          "title": "defaults() doesn't seem to work"
        }
      }
    ]
  }
}

I pasted my query into VSCode, and got my data. Hooray!

But my query was wrong. It worked, sure, but it didn’t have all the data I needed. As I tweaked the query in my editor, I realized I was missing three things that GraphiQL should have provided me:

  1. Introspection
  2. Syntax highlighting
  3. API validation

I assumed I was not the first person to have this problem. I know I had a special case because to JavaScript a GraphQL query is just a string. To perform validation, such a tool needs to know if any given string contains GraphQL.

I set out to find solutions.

A GraphQL plugin for ESLint

As expected, somebody did solve this problem already. The nice people at the Apollo platform have a plugin for ESLint, eslint-plugin-graphql which performs the validation. Not only does it validate syntax, it also validates against a specific GraphQL schema. So, if I misspelled a GraphQL object unique to the GitHub API, like “ropesitory” in the following listing:

ropesitory(owner:"octokit", name:"graphql.js")

. . . ESLint will report the error.

That’s the promise, anyhow. It requires a bit of extra setup to get working (depending on your client) which I’ve detailed below. In particular, my client (@octokit/graphql) accepts plain strings as queries. Regardless of the client, you do need to provide ESLint the GraphQL schema, so it knows which objects are which and where they live.

Configuring eslint-plugin-graphql

To configure eslint-plugin-graphql, I needed to get the GitHub v4 API’s GraphQL schema. The schema is a JSON file which you can derive from the introspection system in the API. Because I was using @octokit/graphql, I was able to use the associated schema on npm (@octokit/graphql-schema), so I didn’t have to use a tool (such as graphql-cli) to download or create it.

  1. I installed these modules for basic support (including peer dependency of graphql):

     $ npm install eslint eslint-plugin-graphql --save-dev
    

    In my case, I also retrieved the schema.json from @octokit/graphql-schema:

     $ npm install @octokit/graphql-schema --save-dev
    
  2. I needed to create a .graphqlconfig file, which is a JSON file containing a schemaPath property whose value is the relative path to schema.json:

     {
     "schemaPath": "node_modules/@octokit/graphql-schema/schema.json"
     }
    
  3. I needed an ESLint config file. I’m partial to using YAML for these (sorry!), so:

     plugins:
       - graphql
     parserOptions:
       ecmaVersion: 2015 # we need template strings
     rules:
       graphql/named-operations: error # requires each operation to be named
       graphql/capitalized-type-name: error # requires each type name is capitalizewd
       graphql/no-deprecated-fields: error # disallows use of deprecated fields
       graphql/template-strings: # defines how GraphQL is recognized
         - error
         - env: literal
           tagName: gql
    

Note: The eslint-plugin-graphql module does not export any “recommended” settings. You need to list each rule (there are only a handful, so it’s not a big deal).

These rules — except for graphql/template-strings — were pretty self-explanatory to me, even with a beginner’s knowledge of GraphQL.

I understood the graphql/template-strings rule defines how the plugin recognizes GraphQL within template strings. I understood that this rule is required unless I was using the Apollo client. In other words, graphql/template-strings is not so much an ESLint rule as it is a plugin configuration.

The env property of the graphql/template-strings rule is a choice of several supported GraphQL client libraries. My queries were raw GraphQL (as you’d use in GraphiQL), so I used literal for the env value.

But I got tripped up on the tagName property. What’s that all about?

Creating a tagged template function

Since I wasn’t using the Apollo client, eslint-plugin-graphql needed another way to determine which of my JavaScript strings contained GraphQL queries. I didn’t want ESLint to complain about JavaScript strings that weren’t GraphQL queries, after all. To do this, I discovered that I needed to use (and implement) a tagged template function.

I needed to prepend this tagged template function to each template string containing a GraphQL query. For example, the query above looked like this, where gql is a tagged template function.

gql`{
  repository(owner:"octokit", name:"graphql.js") {
    issues(last:3) {
      edges {
        node {
          title
        }
      }
    }
  }
}`

Fortunately, the eslint-plugin-graphql README provides one you can copy and paste:

// does not need to be a global function!
global.gql = (literals, ...substitutions) => {
    let result = "";

    // run the loop only for the substitution count
    for (let i = 0; i < substitutions.length; i++) {
        result += literals[i];
        result += substitutions[i];
    }

    // add the last literal
    result += literals[literals.length - 1];

    return result;
}

This is essentially an “identity” function that compiles the template string (and any substitutions) passed into it.

Other strings in my code (e.g., result above) are not interpreted as GraphQL queries because they do not use this tagged template function.

Note that you don’t need to name the function gql. You can use whatever name you wish, as long as it’s a valid function name and specified in the tagName prop of the graphql/template-strings rule (back in the ESLint config file).

After implementing my gql function, I was ready to validate using eslint.

Failure is success

I decided to validate my script (script.js) container using a named operation and my misspelled object:

gql`query LatestRepositoryIssues {
  ropesitory(owner:"octokit", name:"graphql.js") {
    issues(last:3) {
      edges {
        node {
          title
        }
      }
    }
  }
}`

On the command-line:

$ eslint script.js
20:9   error  Cannot query field "ropesitory" on type "Query". Did you mean "repository" or "repositoryOwner"?  graphql/template-strings

Failure is success! Good is bad! Up is down!

Now that this was working, I could set up VSCode.

VSCode configuration (or lack thereof)

The ubiquitous ESLint extension for VSCode will handle GraphQL validation, but not syntax highlighting. I found a nice GraphQL extension for VSCode which worked with my gql function. In fact, given my setup including .graphqlconfig, no further configuration was necessary:

images

Now that’s a great experience.

Conclusion

I’ve taken you through my short journey to getting syntax validation, schema validation, and highlighting working with GraphQL. While perhaps a little fiddly, eslint-plugin-graphql is a powerful tool, and is able to validate your queries regardless of which client you’re using. Happy querying! (. . . and mutating?)