Working With GraphQL Fragments and Mutations

Working With GraphQL Fragments and Mutations

Make your GraphQL queries more dynamic with Fragments, plus get started with Mutations.

    Last week we encountered a genuine scenario when working with GraphQL clients. When building real applications consuming data via GraphQL, we usually don't know precisely the query we're going to want to run at runtime. Imagine a user cruising through your application, setting preferences, and arriving at core pieces of functionality under a content which is specific only to them. Say we're building a GrubHub knockoff (we hate profits and love entering impenetrable parts of the market, it's not that uncommon really.) At its core, the information we're serving will always be restaurants; we'll always want to return things like the restaurant address, name, rating, etc. Because we want our app to be intelligent, this means that circumstances in which User 1 makes a query are vastly different than User 2. Aside from the obvious facts (residing in different locales), perhaps there's more metadata we can leverage from User 1's long-term app usage, versus User 2 who is a total noob to our knockoff app.

    Yet, the information we're serving will always be restaurants. There's a core query being reused at the heart of our requests: we need to be dynamic enough to account for the fact that User 1 has checked off 13 different cuisines and strict delivery time windows, whereas User 2 doesn't give a shit. User 2 just wants pizza.

    This is where GraphQL Fragments come in to play. We've already seen how we can pass variables through our queries to receive contextual data: the next step is creating blocks of reusable code which may never change, which become the foundational building blocks of all future queries.

    When to Use Fragments

    Back to our JIRA example, I demonstrated precisely the sort of thing one should never do: making more than one GraphQL request to serve a single purpose.

    To recap, we're pulling in JIRA issues to a Kanban board. Our board has 4 columns: one per "status." Here's a god-awful way of hardcoding a query like that:

    query JiraIssuesByStatus($project: String, $status: String) {
    	backlog: jiraIssues(where: {project: $project, status: $status}, orderBy: updated_DESC, first: 6) {
    	key
        summary
        epic_color
        epic_name
        status
        priority_rank
        priority_url
        issuetype_name
        issuetype_url
        assignee_name
        assignee_url 
       }
      todo: jiraIssues(where: {project: $project, status: $status}, orderBy: updated_DESC, first: 6) {
    	key
        summary
        epic_color
        epic_name
        status
        priority_rank
        priority_url
        issuetype_name
        issuetype_url
        assignee_name
        assignee_url 
       }
      progress: jiraIssues(where: {project: $project, status: $status}, orderBy: updated_DESC, first: 6) {
    	key
        summary
        epic_color
        epic_name
        status
        priority_rank
        priority_url
        issuetype_name
        issuetype_url
        assignee_name
        assignee_url 
       }
      done: jiraIssues(where: {project: $project, status: $status}, orderBy: updated_DESC, first: 6) {
    	key
        summary
        epic_color
        epic_name
        status
        priority_rank
        priority_url
        issuetype_name
        issuetype_url
        assignee_name
        assignee_url 
       }
     }

    Seems like a lot of repetition, yeah? What if we could define chunks of queries to be reused to simplify things?

    # Write your query or mutation here
    fragment JiraFields on jiraIssue {
      key
      summary
      epic_color
      epic_name
      status
      priority_rank
      priority_url
      issuetype_name
      issuetype_url
      assignee_name
      assignee_url 
    }
    
    query JiraIssuesViaFragments($project: String) {
      backlog: jiraIssues(where: {status: "Backlog", project: $project}, orderBy: updated_DESC, first: 6) {
        ...JiraFields
      }
      todo: jiraIssues(where: {status: "To Do", project: $project}, orderBy: updated_DESC, first: 6) {
        ...JiraFields
      }
      progress: jiraIssues(where: {status: "In Progress", project: $project}, orderBy: updated_DESC, first: 6) {
        ...JiraFields
      }
      done: jiraIssues(where: {status: "Done", project: $project}, orderBy: updated_DESC, first: 6) {
        ...JiraFields
      }
    }

    Progress! Instead of reiterating the fields we want to query for each time, we set these once. We do this by creating a fragment named JiraFields (naming conventions for fragments are totally up to you- these don't relate to anything). To make this easier to visualize, let's just look at the parts:

    fragment [GivenNameToYourFragment] on [DatamodelToQuery(SINGULAR)] {
      [fields] 
    }

    Take note of [nameOfDatamodelToQuery(SINGULAR)]. Our fragment will refer to data model in the singular syntax  - this is important.

    Our New Query Using a Fragment

    Again, let's simply what we're looking at:

    query [GivenNameToYourQuery]($project: String) {
      [subsetName]: [DatamodelToQuery(PLURAL)](where: {status: "Backlog", project: $project}) {
        ...[GivenNameToYourFragment]
      }
    }
    • [subsetName] is the name of the embedded JSON object to be returned in the response. The naming is up to us.
    • [DatamodelToQuery(PLURAL)] contrasts the singular data model we specified in our fragment.
    • Finally, ...[GivenNameToYourFragment] is the syntax for dumping a fragment into a query. Yes, the ... is intentional.

    Here's how we managed to get on:

    Now we're talkin'.

    Implementing On The Client Side

    With the big picture in hand, this is still all theoretical until we have some real code making real dynamic queries. So which GraphQL client tools should we use?!?! Sweet baby Jesus have mercy, I wish I had a straight answer.

    As we all know, Apollo is crushing the game with their seemingly endless libraries doing... a lot of similar stuff? Then there's Prisma, the new hotshot looking to make a buck. But what about this repo? It seems totally fine, but why won't it freakin work?! And what about this Lokka thing? Also, apparently you can just use node-fetch anyway?

    For somebody looking for simplicity, this gets very frustrating. Most clients are immediately concerned with integrating with React as fast as possible (totally understandable), but a small-town country boy like me just wants to start with simple. I'm just trying to write a god damn tutorial!

    Anyway. The GraphQL ecosystem is was it is: we'd be foolish to think anything related to JavaScript could be cohesive or straightforward. Instead of wrestling with that reality, now's as good a time as ever to move on to the part of GraphQL we've failed to speak of: modifying data.

    GraphQL Mutation Cheatsheet

    Any form of creating, changing, or deleting data in GraphQL falls under the umbrella of mutations. The structure is similar to queries, except that we take data in (presumably through variables) and spit out whichever fields you'd like to see as a result of that.

    Creating Records

    A functioning "create" mutation with the resulting response:

    Simple enough.

    And the mutation itself, just in case anybody is copy/pasting out there:

    mutation CreateJiraIssue($key: String!, $summary: String!, $status: String!) {
      createjiraissues(data: {key: $key, status: $status, summary: $summary}) {
        key
        status
        summary
      }
    }

    Updating Records

    We can update records (aka nodes) by specifying the target node using where: {}, and the data to be updated within data: {}

    Syntax is just like creating nodes, but with an added where:{} statement.
    mutation UpdateJIRAIssue($summary: String, $status: String) {
      updatejiraIssue(data: {status: $status, summary: $summary}, where:{key: "HACK-10"}) {
        key
        status
        summary
        project
        issuetype_name
        epic_name
      }
    }

    Deleting Records

    You can even specify which fields you want returned from the node you're in the act of ruthlessly murdering!

    And with its final breath, the node shouted out "Hack-9999, Backlog, Test issue with GraphQL" to please its master once last time.
    mutation DeleteJiraIssue ($key: String!){
        deletejiraissues(where: {key: $key}){
        key
        status
        summary
      }
    }

    Enough For Now

    Hopefully, I'm not the only one to have bee deceived by the simplicity of GraphQL's syntax at first glance. The minimalism of GraphQL queries and mutations would lead one to believe that they're simple to understand right off the bat. The problem with that logic is the syntax is so simplistic, that there's hardly any way of telling what nearly identical queries or mutations might do from one character to the next. Even in JSON, the combination of explicit quotations, key:value relationships, and comma-separation affords us a lot of inferred knowledge we take for granted.

    I'm not saying GraphQL is wrong, or painstakingly difficult to pick up, as much as it can easily be frustrating to newcomers (and rightfully so). As long as people keep reading, I'll keep posting, so let's chip away at this thing week by week.

    Todd Birchard's' avatar
    New York City Website
    Engineer with an ongoing identity crisis. Breaks everything before learning best practices. Completely normal and emotionally stable.

    Engineer with an ongoing identity crisis. Breaks everything before learning best practices. Completely normal and emotionally stable.