In Relay, what role do the node interface and the global ID spec play?

I started out with the relay-starter-kit and also worked my way through the Relay and GraphQL documentation. But there are quite a few areas that are unexplained and mysterious.

Seriously I read a lot of documentations everywhere about all these things but couldn't find any satisfying explanations for the following questions:

What is this for? I put logging but it never even gets called at all:

var {nodeInterface, nodeField} = nodeDefinitions(
  (globalId) => {
    var {type, id} = fromGlobalId(globalId);
    if (type === 'User') {
      return getUser(id);
    } else if (type === 'Widget') {
      return getWidget(id);
    } else {
      return null;
    }
  },
  (obj) => {
    if (obj instanceof User) {
      return userType;
    } else if (obj instanceof Widget) {
      return widgetType;
    } else {
      return null;
    }
  }
);

And what is the actual effect of this:

interfaces: [nodeInterface],

Maybe related to that, what does the node field here do:

var queryType = new GraphQLObjectType({
  name: 'Query',
  fields: () => ({
    node: nodeField,
    // Add your own root fields here
    viewer: {
      type: userType,
      resolve: () => getViewer(),
    },
  }),
});

And what is the magic around the id field? What is globalIdField for?

I have an id in my database and thought I could use it in my GraphQL objects:

Instead of:

id: globalIdField('User'),

I want to use my database id:

id: {
  type: GraphQLID,
  description: 'The identifier'
},

But if I do that I get an error in the browser saying RelayQueryWriter: Could not find a type name for record '1'.

I can get rid of that error by adding __typename to my component containers Relay Query but that seems all wrong.

It would be great if you could give some deeper insides and a better explanation here and enhance the official documentation.

Thank you


Solution 1:

The Node root field, in combination with globally unique IDs, comes into play when Relay needs to refetch an object. Refetching occurs when you call this.props.relay.forceFetch() or when you add fields to the query for an object whose global ID is known because it has already been partially fetched.

In cases like these, Relay will short circuit the regular query and execute a query for the object(s) directly using its global ID and the node root call.

Example:

Assume that $showComments was false when this query was first resolved.

query {
  viewer {
    stories(first: 10) {
      edges {
        node {
          id,
          comments(first: 10) @include(if: $showComments) { 
            author, 
            commentText 
          }
          text,
        }
      }
    }
  }
}

This will have caused a fetch for id and text for some number of stories, whose IDs are now known.

Imagine that at some future time, the variable $showComments became true. Relay will refetch only the data it needs using the node root field.

query {
  node(id: "ABC123") { 
    fragment on Story { comments(first: 10) { author, commentText } }
  }
  node(id: "DEF456") { 
    fragment on Story { comments(first: 10) { author, commentText } }
  }
  node(id: "GHI789") { 
    fragment on Story { comments(first: 10) { author, commentText } }
  }
  ...
}

This depends on a few pieces:

  1. Each object must have a globally unique ID, or be identified by a type/ID pair (the globalIdField helper does this and produces a base64 encoded string).
  2. The server must know how to resolve an object from a globally unique ID, and vice versa. This is what the nodeDefinitions are for.
  3. Any object that hopes to be refetchable using this system must implement the nodeInterface.

See also: https://relay.dev/docs/guides/graphql-server-specification/#object-identification