Validating GraphQL Operations with a GitHub Action
Tags: actionshackathon, graphql, github
Published: 2020-09-06 16:57:12

One of the promises of GraphQL is that it makes it easier to evolve your API on an ongoing basis in comparison to other approaches (like REST APIs). While API maintainers should still avoid making sudden breaking changes to supported types and fields, if you're building a client application it's nonetheless a good idea to make sure that all of the operations it uses can be safely made against the current iteration of the GraphQL API to which it's making its requests.

[GitHub Actions](https://docs.github.com/en/actions) provide a convenient way to hook into specific activities in a GitHub repository (for example, pushing or making a pull request) to ensure the GraphQL operations are valid at the time that event occurs. In this post, I'll outline how I built a custom GitHub Action to perform static analysis of all the GraphQL operations used by a client against a GraphQL schema. I'll also provide a demo of how to use this action in your workflows.

**TL;DR:** You can view the [action repo here](https://github.com/mandiwise/graphql-operation-validation-action) and a [working demo of it in a workflow here](https://github.com/mandiwise/graphql-operation-validation-demo).

## Action Goals & Game Plan

I had the following high-level goals in mind when I created this action:

- Search for and validate all GraphQL operations in either `.graphql` files or in `.js` files that contain the `gql` template tag
- Specify a directory in the project from which to begin recursively searching for operations (to narrow the search)
- Exclude directories or files from this search as needed
- Validate operations against a schema file co-located in the repository or a remote schema
- Send a token in an `Authorization` header when fetching the schema as needed

To accomplish these goals, I leaned heavily on the [GraphQL.js](https://github.com/graphql/graphql-js) and [GraphQL Tools](https://github.com/ardatan/graphql-tools) libraries. Specifically, GraphQL Tools provides functions for loading GraphQL operations from either code files (i.e. `.js` files) or `.graphql` files. It also provides functions for loading a schema from either a `.graphql` file, a `.json` file, or from an URL. The GraphQL.js library provides a function that validates the loaded operation documents against the loaded schema.

I then used the [@actions/core](https://github.com/actions/toolkit/tree/main/packages/core) package to put the pieces together and run the validation whenever the action is triggered and provide some feedback in the GitHub repository interface about the success or failure of the validation check, including what fields or types are invalid.

## Using the Action in a Workflow

You'll first need to create a YAML file to describe [the workflow](https://docs.github.com/en/actions/configuring-and-managing-workflows) in your project (for example, `.github/workflows/operations.yml`).

Next, add a job to the file that uses the [actions/checkout](https://github.com/actions/checkout) package to check out your repository's files (which adds them to the `$GITHUB_WORKSPACE`). You can then run the validation action on the checked-out files as the job's second step.

I designed the GraphQL operation validation action to support the following inputs:

- `base_dir` (required): The base directory to search for operations (relative to repository's root directory)
- `schema_location` (required): An endpoint URL or path of a `.json`/`.graphql` file (relative to repository's root directory)
- `excluded_paths`: A comma-separated list of directory/file paths in the `base_dir` to exclude (relative to `base_dir`)
- `token`: A token to use with an Authorization header (only the Bearer scheme is supported)

This example illustrates how you would validate the GraphQL operations whenever you push or make a pull request to the `main` branch of a repository. The action points to a local schema file in the repository at `swapi/schema.graphql`, looks for operations in the `swapi` directory, and excludes the `swapi/schema.graphql` file when searching for operations documents (because this would result in an error):

```yaml
name: Validate GraphQL Operations
on:
  pull_request:
  push:
    branches:
      - main

jobs:
  operations:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repo
        uses: actions/checkout@v2

      - name: Validate SWAPI operations
        uses: mandiwise/graphql-operation-validation-action@v1
        with:
          schema_location: ${{ github.workspace }}/swapi/schema.graphql
          base_dir: ${{ github.workspace }}/swapi
          excluded_paths: schema.graphql
```

At a minimum, you must specify the `${{ github.workspace }}` as your `base_dir` input when using actions/checkout, but you may wish to narrow the search to a specific subdirectory in the project that all operations are nested below. The `node_modules` directory is automatically ignored when searching for operations, so you don't need to include it in the optional `excluded_paths` input.

Now when you push to the `main` branch or a PR is submitted you can check the Actions tab of your repository on GitHub to see the result. If any operations are invalid, then they will be noted in the log.

### Send an Auth Token

If you need to validate your operations against a schema that requires a token in an `Authorization` header, then you can add it as a `token` input value:

```yaml
# ...
  - name: Validate GitHub GraphQL API operations
    uses: mandiwise/graphql-operation-validation-action@v1
    with:
      schema_location: https://api.github.com/graphql
      base_dir: ${{ github.workspace }}/github
      token: ${{ secrets.ACCESS_TOKEN }}
```

The header will be sent using the `Authorization: Bearer token...` scheme only. You can refer to GitHub's documentation on [using encrypted secrets in a workflow](https://docs.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets#using-encrypted-secrets-in-a-workflow) to safely store your token with the client application's repository.

### Run It Locally

Want to validate your operations in your development environment or without pushing to your repository? No problem!

You can use [act](https://github.com/nektos/act) to run this action locally. After installing act, you can use `act push` to run this action on the previous workflow example in your local environment but without actually pushing any code to GitHub. This can be very handy for testing out changes to your GraphQL operations locally during ongoing development to your client application.

## Good to Know

Because an [introspection](https://graphql.org/learn/introspection/) query is required to get the schema, introspection must be turned on for the GraphQL API you wish to validate your operations against. For this reason, the action is probably best-suited for use with a public GraphQL API, in your local development environment, or when the schema file is co-located with your client application.

## Summary

This action was my first foray into GitHub Actions and I learned a lot throughout the process of developing it. GitHub Actions are very powerful and I found it a bit tricky at first to grok all of the moving parts and know where to start when creating a custom action, but with just a couple hundred lines of code, I hope this action provides a useful reference for you if you're interested in creating your own.

If you use this action in any of your GraphQL-related workflows I'd love to hear any feedback you have about it in the comments on this post or in the repo's [Issues](https://github.com/mandiwise/graphql-operation-validation-action/issues).