Code Oil
Published in

Code Oil

Relay/GraphQL: De-mystifying Node Interface/ID

Summary

Node ID is a ‘mysterious’ little thing in Relay/GraphQL; the Facebook tutorial presented it upfront with little explanation and fanfare, and it ‘just works’. We explain a scenario where it won’t just work, and in the process shed light on when, and how it is actually used by Relay/GraphQL. Armed with that knowledge we hope you can identify other scenarios when it needs more care.

Overview

The Relay/GraphQL tutorial’s chapter on Object Identification elucidates how Relay/GraphQL requires Node IDs for re-fetching objects. If you have implemented Relay in your web application, you would have implemented the interface nodeDefinitions (in Javascript), or its equivalent NodeIdentification (in Ruby), etc. (from hereforth we will use nodeDefinitions as a language-agnostic term). If you have never read the former, nor recall the latter after building your Relay/GraphQL web application, it would not be surprising at all; the standard nodeDefinitions implementation that comes in the tutorial for each Relay/GraphQL library/starter-kit (Javascript, Rails, etc.) should just work, or only require minor tweaks.

To demystify (global) Node ID, one needs to understand:

  • How & when does Node ID gets created?
  • When Node ID is used?
  • How are the nodeDefinitions methods used?

For experienced readers who are just interested in what Node ID is all about without going through any example, feel free to skip to the section Answers & Summary below, and refer back here for detailed explanations, or just refer to the codebase.

Assumptions

Although we provide snippets of explanation throughout, you are expected to be somewhat familiar with the high-level concepts of:

  • GraphQL schema
  • Relay connection/edge
  • React
  • Relay/GraphQL data declaration/co-location

Glossary

To facilitate understanding, we define:

  • GraphQL type: This is like the object-oriented concept of Class. It defines the name of the type, and the fields that the type has. E.g., a User type with fields ‘name’, and ‘age’.
  • GraphQL object: This is like the object-oriented concept of object. It is a specific instance of a Class with specific values for each field. E.g., a User instance can have fields with name ‘John’, and age ‘25’, while another instance can have fields with name ‘Sally’, and age ‘21’. However, both are of the same User type.

Node ID By Example

We will take you through an example to show you why Node ID ‘just works’, and show a counter example when it won’t ‘just work’, followed by a summary of what Node ID is all about.

We motivate our example with a simple problem. We are a denim (also known as jeans) online store. We have a database of each denim’s brand, model, and minimum size. We want to display a list of jeans with those 3 fields to a user visiting the online store.

We follow the flow of Facebook’s tutorial to deconstruct the problem into Relay/GraphQL concepts. Briefly:

  • Build a ‘database’ using Javascript hash to represent the store’s denims. In reality, this can be implemented using ORM, redis, etc.
  • Define a dysfunctional nodeDefinitions, which still somehow ‘just works
  • Define GraphQL schema for GraphQLTypes Denim, DenimList
  • Create a React component that holds logic to show a list of denims
  • Encapsulate the React component with a Relay container to hold the data declaration for the attributes of Denim, DenimList in GraphQL that the component intends to display (component/data co-location)

The details of the implementation follows. The code snippets are littered with comments to facilitate understanding.

Javascript-based Denim Database

GraphQL ‘nodeDefinitions’ Method

Assuming that we have no idea of what nodeDefinitions method should be, we create a dysfuntional nodeDefinitions that return null for all cases.

GraphQL Schema Definition

Import all the Denim database methods that we will use in our GraphQL schema.

In the GraphQL schema, we need to define:

  • Denim (denimType) has 3 attributes, and their respective types
  • DenimList (denimListType) has ‘has many’ Denims (denimType) relationship using Relay connections
  • Denim Relay connection (denimConnection)

Expose the ability to query for denimList as:

React Component

Create the React component (App) to hold the logic for rendering the denim list. For those unfamiliar with Relay/GraphQL connections, the connections introduce the concept of edges, i.e., this.props.denimList.denims give you a list of instances of DenimTypes but to access each instance you need to access them through this.props.denimList.denims.edge[x].node, where x is the index with the array.

Co-located React Component and GraphQL Data Declaration in Relay

Define a Relay container to co-locate both the React component and the GraphQL data declaration to query for the data (DenimList)

The entire codebase is here. We built this example from relay-starter-kit.

Once we run the server, and access it, we get the list of denims, and their attributes as shown:

If we inspect the web browser’s ‘Network’ console, we can see what the data Relay/GraphQL query pulled down from the server. Note that it pulled down the DenimList object with a GraphQL auto-generated global Node ID, as well the list of denims (Relay connections/edges), and their attributes.

Notice that on our NodeJS server console, the nodeDefintions log statements (see nodeDefinitions code above) did not execute, i.e., nodeDefinitions methods were NOT executed during the initial fetch.

When we click ‘reload’ button, which calls this.props.relay.forceFetch() (see React component code above), nothing happens. In fact, on our browser ‘Console’, we can see an error as shown below.

If we look at the ‘Network’ console, we can see that nothing was returned, which was what caused the error.

We can see on the NodeJS console, during re-fetch, the nodeDefinitions methods are called, with the fromGlobalId method correctly mapping the Node ID, used in re-fetch, back to the server-side object type and id.

However, since our dysfunction nodeDefinitions merely return null, that is what the client-side receives.

Most standard nodeDefinitions implementation will just use the server-side object type and id derived by fromGlobalId to retrieve the original server-side object, and return it in response to the re-fetch.

With that fix, when the ‘reload’ button is clicked, you can see from the browser ‘Network’ console that a 2nd GraphQL request is triggered, and the appropriate DenimList data is returned.

These are the answers to the questions we asked at the beginning; they also summarize the things we have learned if you went through the code example above.

How & When Node IDs gets Created?

A Node ID is created for each GraphQL object returned as response if you declare certain keywords pre-defined by the your server-specific GraphQL library (e.g., globalIdField for Javascript, global_id_field for Ruby) when declaring the GraphQL type for that object. Below is an example usage of globalIdField from a GraphQL schema defined in Javascript:

For most implementations of GraphQL, the library will combine the GraphQL type (in the example above, this is the ‘DenimList’ string passed in to globalIdField as parameter) and the specific server-side object’s id to create the object’s Node ID.

When Node ID is used?

As explained in the official Relay/GraphQL tutorial’s chapter on Object Identification, node ID is used for re-fetching, but when does re-fetch happens?

The first GraphQL request to fetch data is NOT a re-fetch, and Node IDs have not been generated yet, thus NodeIdentification methods are NEVER called. A re-fetch happens when on the client-side we already have existing GraphQL objects with Node IDs, and we:

  • Request for more data for an existing GraphQL object, which can arise from another component relying on the same GraphQL object but requiring different pieces of data of that object
  • Call this.props.relay.forceFetch to re-fetch data of a GraphQL object (as demonstrated in our example above), e.g., when you believe that server-side object data has been changed by external code
  • Call this.props.relay.setVariables to change the parameters used to fetch the original GraphQL object, e.g., fetching a profile picture of a different size for the same GraphQL User object

How are ‘nodeDefinitions’ Methods Used?

For GraphQL types that you want to re-fetch, you request GraphQL to auto-generate a Node ID for the object during creation (see above ‘How and When Node IDs are create?’), and you also need to tell GraphQL how to map the Node ID provided during re-fetch into the corresponding GraphQL object. You do this with pre-defined keywords of the language-specific GraphQL library, e.g., forJavascript use ‘interfaces: [nodeInterface]’, for Ruby use ‘interfaces [NodeIdentification.interface]’. Below is a Javascript example:

Note that nodeInterface, and NodeIdentification.interface refers to the actual implementation of nodeDefinitions provided above, which performs the mapping, and has 2 methods:

  • Transform the Node ID provided in re-fetch into a server-side object that represents the same the GraphQL object returned in the original response
  • Identify the GraphQL type given the server-side object

Conclusion

If your object needs to be re-fetched, ensure that you

  • Declare globalIdField on your GraphQL type (in GraphQL schema) to make GraphQL auto-generate a unique global Node ID for your GraphQL object during creation
  • Implement nodeDefinitions methods to map Node ID back to the corresponding server-side object, which you then can use to return other or updated attributes, etc. when a re-fetch provides the Node ID
  • Declare nodeInterface on your GraphQL type to instruct GraphQL to use the nodeDefinitions implementation to resolve between Node ID and actual server-side objects

--

--

Attempts to find better ways to help myself, and others, understand software technology

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Soon Hin Khor, Ph.D.

Use tech to make the world more caring, and responsible. Nat. Univ. Singapore, Carnegie Mellon Univ, Univ. of Tokyo. IBM, 500Startups & Y-Combinator companies