Front-end “Gotchas” of GraphQL

If you have worked in the software industry for any significant amount of time, then you are probably well aware that this old adage rings true.

“The only constant is change.”
— Heraclitus (500 B.C.)

Nathan Smith
by Nathan Smith

It seems that every few months (if not every few days) something new comes along that promises to obviate everything we had come to rely on in the recent past.

Lately, much ink has been spilled on the topic of GraphQL. While I am a fan, as with any technological paradigm shift, GraphQL is not without its own unique set of drawbacks and potential pitfalls.

In a prior blog post, Damien Scott covered GraphQL from a UI service layer vantage point.

In this blog post, I will share my front-end perspective of the niceties and “gotchas” that I encountered when first working with GraphQL. Hopefully, it will be helpful to other developers.

Note: We used Apollo’s flavor of GraphQL on a recent project, so I will cover a few tips that are specific to that as well.

What is the problem?

First of all, for the uninitiated: What is GraphQL?

I like to think of it as a Star Trek lapel pin. You know the drill. A federation starship encounters a new alien species. Despite not being fluent in each other’s specific dialect, a simple tap of the trusty translator pin enables humans and aliens to speak one universal language.

Bear with me, I have a point here.

I am fond of saying to coworkers:

“Microservices [alien languages] are great, except when I am trying to build an app.”

Meaning, if you are building an app and attempting to consume multiple, disparate API endpoints, you essentially need to be a subject matter expert in each of those API’s quirks.

For instance, consider the concept of a “customer” object. In our mind’s eye, we know this represents a real, living, breathing person. I will use myself as an example.

Though my personal info (name, email) might be associated with a customer object, depending on how a company’s data is siloed, my bank accounts, car loan, and/or mortgage might be spread across several different microservices.

As a UI developer, I may not need to know or care about the intricacies of how various API teams decided to divvy up the work of data storage. I simply need to get the “Nathan” object, and all of his associated financial records, to display on a page.

How does GraphQL solve it?

This is where GraphQL comes in. It allows the flexibility to write a unified query for a specific customer, and retrieve all associated data, regardless of where the data originates.

Not only that, but with Apollo, it is possible to do so in a declarative way. Much like how React merges two UI aspects that are implicitly tied to one another (UI logic and UI presentation), Apollo encourages us to move our data fetching into JSX as well.

This approach also allows a component to stay “dumb” (aka: presentational), strictly handling the display of data, without having to know about its source. For the sake of brevity, I will not go into great detail with a ton of code examples. Conceptually, that would look like this.

<Query>
  <DumbComponent />
</Query>

If that component needed to make changes to the data (aka “mutation”), then it might have an additional declarative wrapper like this.

<Query>
  <Mutation>
    <DumbComponent />
  </Mutation>
</Query>

For a more in-depth example, check out this gist.

Furthermore, if multiple components make use of the same (or similar) queries, Apollo will batch them up into a single query. That way, you are not forcing the browser to do multiple HTTP requests for what is essentially the same data.

Suppose one component needs this data:

  • First name
  • Last name
  • Social security number

And another component needs this data:

  • First name
  • Last name
  • Email address

Then only a single HTTP request would be sent, for an object that contains all four data points.

Think of it like a Venn diagram. Apollo detects that “overlap” based on a unique identifier, and is smart enough to realize that we are dealing with the same customer object. It tailors the actual request to envelop all the requisite data points for that customer.

For more on Apollo’s query and mutation components, check out the following links:

https://www.apollographql.com/docs/react/essentials/queries.html

https://www.apollographql.com/docs/react/essentials/mutations.html

Gotchas: Waxing philosophical

According to the scientific principal of mass conservation:

“Mass can neither be created nor destroyed, although it may be rearranged…”

In similar terms, when splitting hairs over how to manage the various tiers of software architecture, I am fond of reminding my coworkers:

“Complexity cannot be wholly eliminated, just moved around.”

Concepts and workflows that are complex — those which get people arguing over the implementation of a solution — are inherently nuanced.

As such, there is rarely a “one true way” solution. Instead, we often talk in terms of compromises and trade-offs. When determining whether GraphQL is right for your project, it really comes down to a team consensus around this question:

“Where do we want to pay the complexity tax?”

Chiefly, you should make sure that a complexity tax actually needs to be paid.

If your API just so happens to encapsulate all the data that you might need, as a single endpoint, then adding GraphQL might be overkill. Everything may already be as unified as desired. If so, then a simple window.fetch is probably good enough.

When evaluating the possibility of using GraphQL, I would encourage you to audit your microservices as well. Assuming you have the wherewithal to make changes, some of the API pain you may be attempting to alleviate via GraphQL could possibly be addressed by better structuring your microservices first.

Rather than introduce another tier (GraphQL middleware), maybe fix the tiers you have.

aka: Do not reach for a screwdriver, if what you really need is to repair your hammer.

Gotchas: Null vs. Undefined

One big “duh!” moment for me came when using object destructuring with GraphQL. While this is really an issue with JavaScript, it is made more acute by how GraphQL handles nonexistent values.

For a typical Ajax request, if a response has a key/value that does not exist, you would simply expect to get undefined when peeling it apart. As such, by supplying a fallback when doing object destructuring, that would be used instead.

However, GraphQL returns null for anything you have requested for that does not have a value. In which case, null is still used instead of your fallback. This can be maddening if you are not aware of what is happening.

Consider the following example.

// Render method.
render () {
  // Set fallback.
  const fallback = {
    child: 'no data'
  }

  // Get whatever.
  const {whatever = fallback} = this.props

  /*
    Assuming `this.props.whatever` was
    passed from a parent component, but
    did not exist in the Ajax response:

    - We would expect "no data" to be logged.

    - GraphQL gives us `null` so this would
      cause an error because `child` is not
      a property that exists on `null`.
  */
  const {child} = whatever
  console.log(child)
}

Note: This also applies to a component’s defaultProps. If an incoming prop’s value is explicitly set to null, then the default fallback will not be used. Again, this is simply how JavaScript (and React) work. Still, the insertion of otherwise unexpected null values can be tricky to track down, if you are oblivious about what GraphQL is doing for you.

It is a simple enough issue to work around, as long as you know that you need to solve for it. The following example would mitigate this problem

The reason this works is that the “||” syntax applies to anything that evaluates to falsy, so it handles both null and undefined values.

// Render method.
render () {
  // Set fallback.
  const fallback = {
    child: 'no data'
  }

  // Get whatever.
  const {whatever} = this.props

  // Get child.
  const {child} = (whatever || fallback)

  // Reliably logs "no data".
  console.log(child)
}

Explanation: Truthy vs. Falsy

As a rule of thumb, it is helpful to remember that React defaultProps and object destructuring fallbacks only work when a value is both undeclared and implicitly falsy.

Meaning, any values other than undefined will be used verbatim, and your fallback will be unused as a result.

Think of it as the various UI states of a toilet paper roll:

  1. Spool has paper
  2. Spool is empty
  3. Spool is missing
  4. Spool holder is missing

In JavaScript terms, using a destructuring fallback covers only scenario 4, whereas the “||” approach covers all falsy scenarios: 2, 3, and 4.

UI states of a toilet paper roll

What about Redux?

If you are using React, but not (yet?) using GraphQL, chances are that you are familiar with Redux “thunks” as a way of fetching data.

In fact, this was the approach we used for several enterprise React projects, before becoming enamored with Apollo’s declarative and approach.

If you are considering GraphQL for a new project, I would caution against attempting to use it alongside Redux. While not mutually exclusive, I feel like they solve slightly different use cases in overlappingly similar ways. So much so, that if you are using GraphQL, I would even go so far as to say you should not use Redux (and vice versa).

For an app we built recently, I found that we were able to accomplish everything we needed by using GraphQL for data queries/mutations, and managed UI changes using local state in higher order components.

One common misconception is that local state is verboten, and instead Redux should always be used for anything stateful. Coming from a “manage everything in Redux” approach, it was a refreshing change to have UI state logically collocated with its relevant component.

For more on this topic, read: How GraphQL Replaces Redux.

Gotchas: Overhead

If you have read this far, then you might be thinking that GraphQL is something you would like to look into further. I would be remiss if I did not also mention the implicit overhead of adopting a technology with new syntax.

  1. Learning — Though it is a mental hurdle worth jumping over (onto a bandwagon?), GraphQL has a learning curve just like anything else. Once you have an “aha” moment, it is not too bad. Up until that point, it can feel a bit foreign.
  2. Tooling — You will need to ensure your text editor or IDE of choice supports syntax highlighting for GraphQL. Though not strictly necessary, it makes for a more pleasant development experience. Thankfully, there are a myriad of plugins available that add support for GraphQL string literals.
  3. Bundle size — Granted, this is a cost not often talked about since the benefits usually far outweigh the raw file size, but is a valid concern nonetheless. If you simply did regular HTTP requests, an extra parsing library would be unnecessary.
  4. HTTP verbs — GraphQL really speaks only GET and POST. Though not a huge concern from a purist standpoint, it may lead to confusion over which aspect of CRUD (create, read, update, delete) is being done if simply inspecting web traffic.
  5. Caching — Somewhat related to HTTP verbs, depending on how you are serving your API data, there is an opportunity for caching to be had. For example, if you have a GET endpoint that serves some data, and its params are constant, then a cache could possibly serve up an identical response if queried within a certain timeframe threshold. If you are doing lots of traffic over POST, you may not be able to reap these benefits.
  6. Performance — Tangential to caching, though a concern unto itself, is that of actual query performance. By this, I mean the way you choose to ping your microservices. GraphQL makes things inherently more performant on the front-end, but make sure that while you are deciding how to handle the middleware that you do so in a way that does not needlessly burden the RESTful API endpoints.
  7. Infrastructure — Perhaps this should go without saying, but you would be surprised how many mentions of GraphQL are initially met with enthusiasm, only to arrive an impasse when reality sets in. You will need to run GraphQL, usually via Node.js as a middle tier, between your apps and APIs in production.

For more reading on GraphQL HTTP and caching, check out the links below:

https://graphql.org/learn/serving-over-http

https://blog.apollographql.com/caching-graphql-results-in-your-cdn-54299832b8e2

Note: If for some reason the “infrastructure” concern is a non-starter, but you still want to use Apollo GraphQL (and the “Graphi-i-QL” UI) on the front-end, check out Apollo Bridge Link.

Personally, I believe that using Apollo Bridge Link is worth it, even if you are ultimately just hitting RESTful endpoints. It at least makes tinkering with APIs in the browser via GraphiQL possible. In my opinion, this is greatly preferable to using something like Swagger.

Conclusion

Okay, so you have reached the end. What now?

Should you definitely use GraphQL in your next project?

Honestly, as with every software design/dev question, the answer is: “It depends.”

Only you and your team can determine that with any degree of certainty. There will always be trade-offs, and anything worthwhile usually has some level of difficulty attached to it.

However, I would encourage you to at least kick the tires and give it a try. You may find that it jives with your workflow too. If not, then at least you will have an informed opinion as to why.

Interested in partnering with us?

Send a message and we will work with you to understand your needs.

UX360 - Enterprise Journey Mapping Platform

Power Platform

UX360 - Enterprise Journey Mapping Platform
Related Insights from Our Experts

Front-end “Gotchas” of GraphQL

If you have worked in the software industry for any significant amount of time, then you are probably well aware that this old adage rings true. "The only constant is change." — Heraclitus (500 B.C.) by Nathan Smith [...]

4 Steps to Customer-Centered Digital Strategy

While C-suite executives increasingly recognize that customer experience (CX) is an engine for growth, success is often elusive. Challenges include the lack of a holistic approach, organizational silos, and outdated technologies. An exclusive focus on the front-end experience—rather than [...]

What Great Data Can Do for Design

When designers think of great design, quantitative data rarely features in the imagination. Because experienced designers know that the most surprising and insightful moments will often come from some of the briefest and most nuanced interactions. They are weary [...]

Related Consulting Solutions

Journey Mapping

Visualize your customer’s pain points and gaps, and create the future state customer journey. We put our tried and true journey mapping methodology to work to align your organization around your customer and use our UX360 platform to help create, store and share these assets.

Customer & User Research

Ground your CX initiatives on real insights uncovered via contextual inquiry.

2018-09-20T14:34:28+00:00