GraphQL Fragments: How to Simplify Your API Definitions

Eric Goebelbecker
|
May 11, 2022

Many developers see GraphQL as the next logical step forward from RESTful APIs. It's introspective, so you can programmatically retrieve a description of the underlying data. More importantly, you can submit queries for only the fields you want to retrieve and save on time and bandwidth.

But GraphQL requests can get large and ponderous, especially when you're requesting more than one item with the same subset of fields. Those queries can get noisy and complicated. That's where GraphQL Fragments come in. They let you name a subset of a query and reuse it by referring to the name.

Let's look at how GraphQL Fragments work and how you can use them to make your code more modular, easier to read, and easier to maintain.

What Are GraphQL Fragments?

A picture is worth a thousand words, so a code sample must be worth at least a few hundred. So, instead of describing a fragment, let's look at one in action.

Here, we're querying the GitHub API for the first ten issues on Facebook's REACT repository:

{

 repository(owner: "facebook", name: "react") {

   name

   issues(first: 10) {

     nodes {

       author {

         login

       }

       closed

       createdAt

       body

     }

   }

 }

}

We only want the issues, and for each entry, we only want the author login, the issue status, when the author entered it, and the body. GraphQL makes this easy.

But we're working on an app that will display this from more than one repo. So the queries start to look like this:

query repoissues {

 repo1: repository(owner: "facebook", name: "react") {

   name

   issues(first: 10) {

     nodes {

       author {

         login

       }

       closed

       createdAt

       body

     }

   }

 }

 repo2: repository(owner: "protocolbuffers", name: "protobuf") {

   name

   issues(first: 10) {

     nodes {

       author {

         login

       }

       closed

       createdAt

       body

     }

   }

 }

 repo3: repository(owner: "kubernetes", name: "kubernetes") {

   name

   issues(first: 10) {

     nodes {

       author {

         login

       }

       closed

       createdAt

       body

     }

   }

 }

}

That's hard to read and a waste of bandwidth as the number of repositories grows.

So, here's where GraphQL Fragments come to the rescue.

We can declare part of a query with a name and reuse it when we want to match the associated set of fields.

fragment issueinfo on Repository {

 issues(first: 10) {

   nodes {

     author {

       login

     }

     closed

     createdAt

     body

   }

 }

}

Then, we can use it when we want to query for some repos and issues:

query repoissues {

 repo1: repository(owner: "facebook", name: "react") {

   name

   ...issueinfo

 }

 repo2: repository(owner: "protocolbuffers", name: "protobuf") {

   name

   ...issueinfo

 }

 repo3: repository(owner: "kubernetes", name: "kubernetes") {

   name

   ...issueinfo

 }

}

That's what a fragment is! So, what are they good for?

Why Use Fragments?

DRY and Readable Code

Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live. - John F. Woods

We don't write code for computers. We write it for our coworkers and ourselves. If GraphQL Fragments do nothing more than make queries easier to read, they're worth using. And they certainly do that. Even in the brief example above, the fragment doesn't just compress 41 lines of code down to 26. It makes the code easier to digest. It tells us we're looking for information about issues for the three repositories.

Don't Repeat Yourself (DRY) is one of the cardinal rules of programming, and Fragments are built into GraphQL to help you with that. Whenever you find yourself querying for a set of fields, you should consider attaching a name to that query and setting aside a fragment for reuse.

Modularity and Efficiency

An application like our imagined issue viewer generates queries on the fly. For example, the application queries the API for a list of repositories and uses that list to build a selector for the user. When the user selects one or more repos, the application builds a new query like the one we've been looking at.

At the same time, the application allows users to select the issue fields they want. Then, it needs to feed the user's selections into the new query. Without GraphQL fragments, that means rebuilding the entire query string for each repository based on the user's selections.

With fragments, your application only needs to update the fieldset in one place. The repo queries remain the same since they need only refer to the fragment name. The user could select a completely different set of issue fields, and only the fragment would change.

API Support

Fragments are useful enough that several GraphQL APIs include support for them.

APIs like Apollo and Gatsby make it easy to manage fragments in separate, version-controlled files. So, you can avoid large queries and treat your fieldsets like first-class named objects.

Even if the API you're working with doesn't explicitly support fragments, you can write code to build queries with them. GraphQL considers them named sections of queries, and you should, too.

GraphQL Fragments in Action

Let's look at how we can use GraphQL Fragments to make life easier. To focus on the queries and not on language and API specifics, we'll use GitHub's explorer to query their public API.

Basic GraphQL Fragment Usage

We left off with a simple query and fragment for looking at repositories and issues:

fragment issueinfo on Repository {

 issues(first: 10) {

   nodes {

     author {

       login

     }

     createdAt

     body

     closed

   }

 }

}

query repoissues {

 repo1: repository(owner: "facebook", name: "react") {

   name

   ...issueinfo

 }

 repo2: repository(owner: "protocolbuffers", name: "protobuf") {

   name

   ...issueinfo

 }

 repo3: repository(owner: "kubernetes", name: "kubernetes") {

   name

   ...issueinfo

 }

}

This pulls a lot of information about those three repositories:

{

 "data": {

   "repo1": {

     "name": "react",

     "issues": {

       "nodes": [

         {

           "author": {

             "login": "jriecken"

           },

           "createdAt": "2013-05-30T03:46:02Z",

           "body": "I'm trying to programatically invoke the JSX transformer <snip>",

           "closed": true

         },

         {

           "author": {

             "login": "zpao"

           },

           "createdAt": "2013-05-30T04:32:10Z",

           "body": "At the very very least, it should not be broken when we require it.\n",

           "closed": true

         },

<snip>

The fragment makes it easy to add or remove fields. In this example, we're only pulling four fields for each issue.

Here's a situation where a fragment starts to reduce query size and save bandwidth:

fragment issueinfo on Repository {

 issues(last: 10) {

   nodes {

     title

     repository {

       id

     }

     bodyText

     bodyUrl

     comments(first: 10) {

       nodes {

         author {

           login

         }

         body

       }

     }

   }

 }

}

query repoissues {

 repo1: repository(owner: "facebook", name: "react") {

   name

   ...issueinfo

 }

 repo2: repository(owner: "protocolbuffers", name: "protobuf") {

   name

   ...issueinfo

 }

 repo3: repository(owner: "kubernetes", name: "kubernetes") {

   name

   ...issueinfo

 }

}

We're retrieving the first ten issues and the first ten comments for each issue. The fragment has 18 lines of code, so for just three repos, we compressed 54 lines down to 21. Plus, we could add comments to this query by only touching the fragment.

Here's a snippet of output:

{

 "data": {

   "repo1": {

     "name": "react",

     "issues": {

       "nodes": [

         {

           "title": "Bug: ",

           "repository": {

             "id": "MDEwOlJlcG9zaXRvcnkxMDI3MDI1MA=="

           },

           "bodyText": "React version:\nSteps To Reproduce\n\n\n\n\n\nLink to code example:\n\nThe current behavior\nThe expected behavior",

           "bodyUrl": "https://github.com/facebook/react/issues/24336#issue-1199912483",

           "comments": {

             "nodes": []

           }

         },

         {

           "title": "Bug: useEffect is not getting triggered from an outside component's redux dispatch action",

           "repository": {

             "id": "MDEwOlJlcG9zaXRvcnkxMDI3MDI1MA=="

           },

           "bodyText": "React version: 18.0.0-rc.3\n\n  const product = useSelector(state => state.selectProduct);\n <snip>",

           "bodyUrl": "https://github.com/facebook/react/issues/24337#issue-1200079359",

           "comments": {

             "nodes": [

               {

                 "author": {

                   "login": "rickhanlonii"

                 },

                 "body": "@umuralpay this is likely to be an issue with your Redux version, but if you're using the version compatible with React 18, please file an issue with Redux with a full reproduction. "

               }

             ]

           }

         },

         {

           "title": "Bug: TS7016: Could not find a declaration file for module 'react-dom/client'",

           "repository": {

             "id": "MDEwOlJlcG9zaXRvcnkxMDI3MDI1MA=="

           },

           "bodyText": "After updating react, I started getting messages in the logs\nReactDOM.render is no longer supported in React 18. <snip>",

           "bodyUrl": "https://github.com/facebook/react/issues/24345#issue-1200581022",

So, you can see how easy it is to modify a query via a fragment. If your application knows to work with fragments, you have code that you can reuse for nearly any kind of query.

GraphQL Fragments and Variables

Based on what we've already seen, fragments simplify queries, especially large ones that differ between executions. But fragments support variables, too! So, we can change how a fragment works without modifying it.

Let's trim the query fields back and add a variable for the number of issues we get for each repository.

fragment issueinfo on Repository {

 issues(first: $issuecount) {

   nodes {

     state

     title

   }

 }

}

query repoissues ($issuecount: Int=3) {

 repo1: repository(owner: "facebook", name: "react") {

   name

   ...issueinfo

 }

}

In the fragment, we replaced the count with a variable named $issuecount. Then, we added the same variable to the query as a parameter, with the count we wanted.

Here are the results:

{

 "data": {

   "repo1": {

     "name": "react",

     "issues": {

       "nodes": [

         {

           "state": "CLOSED",

           "title": "Can't require() react-tools module"

         },

         {

           "state": "CLOSED",

           "title": "Write tests for react-tools module"

         },

         {

           "state": "CLOSED",

           "title": "must adding comments for JSX?"

         }

       ]

     }

   }

 }

}

You're not limited to one variable, either. Let's switch things around to the last N issues with a user-specified status.

fragment issueinfo on Repository {

 issues(last: $issuecount, filterBy: {states: $issuestate}) {

   nodes {

     title

   }

 }

}

query repoissues($issuecount: Int = 3, $issuestate: String="OPEN") {

 repo1: repository(owner: "facebook", name: "react") {

   name

   ...issueinfo

 }

}

Here are the last three open issues as of the time I ran the query:

{

 "data": {

   "repo1": {

     "name": "react",

     "issues": {

       "nodes": [

         {

           "title": "Bug: State updates in ResizeObserver callbacks are applied after paint and cause visual glitches"

         },

         {

           "title": "Suggestion: Process higher priority renders after each useEffect callback"

         },

         {

           "title": "Compatible issue about createRoot between 17 & 18"

         }

       ]

     }

   }

 }

}

Nesting Fragments

You can nest fragments and pass variables to "inner" fragments while you're at it.

Let's move the comment fields out to their own fragment and set a variable for their count.

fragment commentinfo on Issue {

       comments(first: $commentcount) {

       nodes {

         author {

           login

         }

         body

       }

     }

}

fragment issueinfo on Repository {

 issues(last: $issuecount, filterBy: {states: $issuestate}) {

   nodes {

     title

     repository {

       id

     }

     bodyText

     bodyUrl

     ...commentinfo

   }

 }

}

query repoissues($issuecount: Int=1, $commentcount: Int=1, $issuestate: String="OPEN") {

 repo1: repository(owner: "facebook", name: "react") {

   name

   ...issueinfo

 }

}

This yields an issue and its last comment. You can play with the counts to see different results in the explorer.

{

 "data": {

   "repo1": {

     "name": "react",

     "issues": {

       "nodes": [

         {

           "title": "Compatible issue about createRoot between 17 & 18",

           "repository": {

             "id": "MDEwOlJlcG9zaXRvcnkxMDI3MDI1MA=="

           },

           "bodyText": "Hi,\nOur component lib Ant Design modal <snipped for length>.",

           "bodyUrl": "https://github.com/facebook/react/issues/24347#issue-1201335415",

           "comments": {

             "nodes": [

               {

                 "author": {

                   "login": "nihgwu"

                 },

                 "body": "I think you can use `ReactDOM.version` to decide which api to use"

               }

             ]

           }

         }

       ]

     }

   }

 }

}

You could easily expand this query to tens or hundreds of repos and then tweak the fields you need by only playing with the two fragments.

GraphQL Fragments Mean Cleaner Code

This article showed how GraphQL Fragments make your queries and application code easier to manage. They're a tool for breaking GraphQL queries down into named fieldsets that you can reuse, modify, and store for future reference. GraphQL APIs can get complicated, but fragments make it easier for you to wrap your head around complex queries. Try them out and see how they can make it easier to manage your GraphQL applications.

This post was written by Eric Goebelbecker. Eric has worked in the financial markets in New York City for 25 years, developing infrastructure for market data and financial information exchange (FIX) protocol networks. He loves to talk about what makes teams effective (or not so effective!).

Download Blog Post

The Inside Trace

Subscribe for expert insights on application security.

Thanks! Your subscription has been recorded.

or subscribe to our RSS Feed

Read more

See Traceable in Action

Learn how to elevate your API security today.

Read more