How not to GraphQL

2 min read

If you're here you're probably second-guessing your usage of GraphQL. It's a great tool but if your not deliberate about your schema design it can create complexity. A misuse of it can happen in the following way:

The design of your GraphQL Schema leads to highly dynamic queries

Imagine we are building a spreadsheet-like table where the existence of each column is determined by the user. They can add new columns with a plus button.

Table

Ideally, we want to avoid over-fetching of data and only request what's needed to display the columns. We can do this with the @includes directive to make our queries dynamic.

query issueQuery($tags: Boolean!) {
  issues {
    tags @include(if: $tags) {
      name
      color
    }
  }
}

But this becomes complex when almost every property needs to be dynamically included when dealing with multiple levels of hierarchy.

query issueQuery($commenters: Boolean!, $commentersComments: Boolean!) {
  issues {
    commenters @include(if: $commenters) {
      name
      comments @include(if: $commentersComments) {
        text
      }
    }
  }
}

You might be tempted to do string concatenation (query generation) to create your GraphQL query and avoid the @include directive altogether.

const includeCommenters = true
const includeCommentersComments = true

const query = `
  query issueQuery
    issues {
      ${includeCommenters ? `commenters {
        name
        ${includeCommentersComments ? `comments {
          text
        }` : ``}
      }` : ``}
    }
  }
`

But you will lose some of the benefits you get with GraphQL in its static form:

  • Tools that statically analyze GraphQL

    • Generating typescript from GraphQL
    • Inspecting the schema from your text editor
  • Developer clarity on the data that will be returned from a query

The benefit you get from GraphQL when generating the query is not over-fetching data.

A solution

But wait. There's a way to get the benefits of GraphQL for our dynamic table. If you control the GraphQL Schema — meaning It's not a third party service — then you can change the Schema to better suit the display.

query tableQuery($columns: String[]!) {
  dynamicTable(columns: $columns) {
    rows {
      columns {
        type
        ... on Title {
          title
        }
        ... on Tags {
          name
          color
        }
        ... on PullRequest {
          title
          link
        }
      }
    }
  }
}

With the use of inline-fragments, the returned shape of data is clear. If the columns argument included "Tags" then it would return that column in the response.

// If we ran the query with the argument
const $columns = ["Tags"]

// It would return this
const response = {
  customTable: {
    rows: [
      {
        columns: [{ type: "Tags", name: "Bug", color: "red", id: 100 }],
      },
      {
        columns: [{ type: "Tags", name: "Improvement", color: "yellow", id: 111 }],
      },
    ],
  },
}

This approach radically simplifies our frontend code allowing us to write simpler components.

Was this article helpful?

I'm writing an ebook called Robust UI. Subscribe to get chapters from it via email