GraphQL: understanding node interface.
Tags: graphql, relay, webdev, tutorial
Published: 2020-04-15 19:04:49

## Overview
To provide options for GraphQL clients to elegantly handle for caching and data refetching GraphQL servers need to expose object identifiers in a standardized way. In the query, the schema should provide a standard mechanism for asking for an object by ID. We refer to objects with identifiers as _nodes_.

## Reserved Types
To a GraphQL Server to be compatible, it must reserve certain types and type names to support the consistent object identification model. We should create the following types:
* An interface named _Node_
* The _node field_ on the root query type

## Node Interface 
The server must provide an interface called _Node_. That interface must include exactly one field, called _id_ that returns a non-null _ID_. This _id_ should be a globally unique identifier for the object, and with just this _id_, the server should be able to refetch the object.

NOTE: If two objects appear in the query, both implementing the _Node_ with identical _ids_, then the two objects must be equal.

## Node root field
The server must provide a root field named _node_ that returns the _Node_  interface. This root field must take exactly one non-null _ID_ argument called _id_.

When the value returned by the server in the _Node_'s _id_ field is passed as the _id_ parameter to the _node_ root field than this query should refetch the identical object that implements _Node_.

Now that we have some insight we must answer the following questions:
* How and when does Node ID gets created?
* When Node ID is used?
* How are the nodeDefinitions methods used?

These questions were asked in this post [Relay/GraphQL: De-mystifying Node Interface/ID](https://medium.com/code-oil/relay-graphql-de-mystifying-node-id-38757121b9c). The present post also answers theses questions besides showing more examples, a generic and scalable form of registering types, how to test your _nodeField_ and a generalization of the _Node Interface_ pattern.

## How and when the Node IDs gets created?
A _Node ID_ is created for each GraphQL object declaring _GraphQLObjectType_ for that object. The function _globalIdField_ has the role of auto-generate the node ID when the GraphQL object is created.

```ts
import { globalIdField } from 'graphql-relay';
import { IUser } from './UserModel';
import { load } from './UserLoader';

const UserType = new GraphQLObjectType<IUser, GraphQLContext>({
  name: 'User',
  description: 'User data',
  fields: () => ({
    //  auto-generate node ID when the GraphQL object is created
    id: globalIdField('User'),
    ...
  }),
  ...
});

export const UserConnection = connectionDefinitions({
  name: 'User',
  nodeType: GraphQLNonNull(UserType),
});

export default registerTypeLoader(UserType, load);

```
Further, we will talk about _registerTypeLoader_.

## When the Node ID is used?
Well, we already know that _Node ID_ is used for refetch. But when exactly does a refetch occurs?
The First GraphQL request to fetch data is not a refetch and _Node IDs_ have not been generated so far. The refetch occurs when on the client-side we already have GraphQL objects with _Node IDs_ and, for instance: 
* we request 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
* when the server-side object data has been changed 
* when to change the parameters used to fetch the original GraphQL object.

## How are nodeDefinitions methods used?
For GraphQL types that you want to refetch you request to auto-generate a _Node ID_ for the object during creation, as seen above, and you also need to tell GraphQL how to map globally the _Node ID_ provided during refetch into actual data objects and their GraphQL types. You do this defining the _interfaces_ field on _GraphQLObjectType_.
```ts
// UserType.ts
import { globalIdField } from 'graphql-relay';
import { IUser } from './UserModel';
import { load } from './UserLoader';
import { nodeInterface } from '../../interface/NodeDefinitions';

const UserType = new GraphQLObjectType<IUser, GraphQLContext>({
  name: 'User',
  description: 'User data',
  fields: () => ({
    id: globalIdField('User'),
    ...
  }),
  // use nodeInterface for re-fetching this GraphQL type by node ID
  interfaces: () => [nodeInterface],
});

export const UserConnection = connectionDefinitions({
  name: 'User',
  nodeType: GraphQLNonNull(UserType),
});

export default registerTypeLoader(UserType, load);
```

The _nodeDefinitions_ has this format and export _nodeField_, _nodeFields_ and _nodeInterface_.

```ts
// nodeDefinitions.ts
import { nodeDefinitions } from 'graphql-relay';
import { idFetcher, typeResolver } from './nodeRegistry';

export const { nodeField, nodesField, nodeInterface } = nodeDefinitions(
  idFetcher,
  typeResolver,
);
```

Be aware that _nodeDefinitions_ to tell GraphQL how performs the mapping the _Node ID_ and has two methods:
* the first receive the _globalId_, we map the _globalId_ into its corresponding data object and with it can actually be able to read the GraphQL type and id of the object using _fromGlobalId_ function. We will call it the _idFetcher_.
* the second receive the result object and Relay uses that to map an object to its GraphQL data type. So if the object is an instance of User, it will return UserType and so on. We will call it the _typeResolver_.

See below how to implement the methods explained above:
```ts
// nodeRegistry.ts - that not scale well
import { fromGlobalId } from 'graphql-relay';
import User, * as UserLoader from '../modules/user/UserLoader';
import UserType from '../modules/user/UserType';
// many imports...

export const idFetcher = async (globalId: string, context: GraphQLContext) => {
  const { id, type } = fromGlobalId(globalId);

    if (type === 'User') {
      return UserLoader.load(context, id);
    }
    // many ifs...
    // all the loads of your project
    ...

    return null;
};

export const typeResolver = object => {
    if (obj instanceof User) {
      return UserType;
    }
    // many ifs...
    // all the types of your project
    ...

    return null;
}
```
Realize that this implementation can be improved.

After that, expose the _node_ and your other queries on _QueryType_:
```ts
// QueryType.ts
import { GraphQLID, GraphQLString, GraphQLNonNull, GraphQLObjectType, GraphQLInputObjectType, GraphQLInterfaceType } from 'graphql';
import { connectionArgs, fromGlobalId, globalIdField } from 'graphql-relay';

import { nodeField, nodesField } from '../interface/NodeDefinitions';
import { GraphQLContext } from '../types';

import UserType, { UserConnection } from '../modules/user/UserType';
import { UserLoader } from '../loader';

export default new GraphQLObjectType<any, GraphQLContext>({
  name: 'Query',
  description: 'The root of all... queries',
  fields: () => ({
    id: globalIdField('Query'),
    node: nodeField,
    nodes: nodesField,
    users: {
      type: GraphQLNonNull(UserConnection.connectionType),
      args: {
        ...connectionArgs,
        filters: {
          type: new GraphQLInputObjectType({
            name: 'UserFilters',
            fields: () => ({
              search: {
                type: GraphQLString,
              },
            })
          }),
        }
      },
      resolve: (_, args, context: GraphQLContext) => UserLoader.loadAll(context, args),
    },
    ...
  }),
});

```

On playground take any _id_ returned by _UserList_ and pass it to _node_ query, as this:
```ts
query Node {
    node(id: "VXNlcjo1ZTgyNGM4YTUwZjcwMzdhMjhmZDdmZWU=") {
    id
    __typename
  }
}
```
The result on `__typename` should be the `User` indicating the GraphQL type of the node.
Well, you just realized how both _Node ID_ and _nodeDefinitions_ are used \o/.

Now, we will go into a part about how you can improve its implementation.

## Register Type Loader
To all GraphQL types that the server should be able to refetch you must register your `type` and `load` GraphQL. 
This may involve many imports, many ifs and fast growth of `nodeRegistry` as it was shown previously. We want to prevent this situation because it does scale well.
In order to avoid the manual register on the `nodeRegistry` file, we use an object named _typesLoaders_ that assigns a `type` and a `load` GraphQL through a function called _registerTypeLoader_. See again the snippet `UserType.ts` how simple it is to use it. So, the `nodeRegistry` just uses that object bringing it with the function `getTypesLoaders`. Check down its implementation:

```ts
// registerTypeLoader.ts
import { GraphQLObjectType } from 'graphql';
import { Load } from '../types';

type TypeLoaders = {
  [key: string]: {
    type: GraphQLObjectType,
    load: Load,
  };
};

const typesLoaders: TypeLoaders = {};
export const getTypesLoaders = () => typesLoaders;
export const registerTypeLoader = (type: GraphQLObjectType, load: Load) => {
  typesLoaders[type.name] = {
    type,
    load,
  };

  return type;
};
```
Use that function to type that you want that should be able to refetch, for instance: `UserType.ts`, `PostType.ts`, `TransacationType.ts` and so on.

```ts
// nodeRegistry.ts - clean implementation
import { fromGlobalId } from 'graphql-relay';
import { getTypesLoaders } from './registerTypeLoader';
import { GraphQLContext } from '../types';

const typesLoaders = getTypesLoaders();
// How to return an object given its globalId
// Is used to take the ID argument of the node field and use it to resolve an object
export const idFetcher = async (globalId: string, context: GraphQLContext) => {
  const { type, id } = fromGlobalId(globalId);
  const data = await typesLoaders[type].load(context, id);

  return data;
};

// How to return a GraphQL type given an object
// Allows your GraphQL server to work out which GraphQL type of object was returned
export const typeResolver = object => typesLoaders[object.constructor.name].type || null;
```

Much more structured and clean code, isn't it?

## Test node field
To test if your _node_ query works you can try a test by an inline fragment or spread fragment. Here we will show both. 

```ts 
// NodeDefinitions.spec.ts
import { graphql } from 'graphql';
import { toGlobalId } from 'graphql-relay';
import { schema } from '../../schema/schema';

it('should load User by inline fragment', async () => {
  const user = await createUser();

  const query = `
    query Node($id: ID!) {
      node(id: $id) {
        id
        ... on User {
          name
          username
          isActive
          __typename
        }
      }
    }
  `;

  const rootValue = {};
  const context = await getContext();
  const variables = {
    id: toGlobalId('User', user.id),
  };

  const result = await graphql(schema, query, rootValue, context, variables);
  expect(result.data!.node.id).toBe(variables.id);
  expect(result.data!.node.name).toBe(user.name);
  expect(result.data!.node.username).toBe(user.username);
  expect(result.data!.node.isActive).toBe(user.isActive);
  expect(result.data!.node.__typename).toBe('User');
});
```
Like we already registered the GraphQL type `User` and define the _nodeField_ on `Query` we can test if the _node_ brings the data correctly. First we create a user, after we define the query, a root value which can be an empty object, a context which must be similar to the passed on the your server, the variables that your query needs in that case the _id_. To execute the query we put that informations into `graphql` and finally we can to compare the returned data to verify if it is correct or not.

```ts 
// NodeDefinitions.spec.ts
import { graphql } from 'graphql';
import { toGlobalId } from 'graphql-relay';
import { schema } from '../../schema/schema';

it('should load User by spread fragment', async () => {
  const user = await createUser();

  const query = `
    query Node($id: ID!) {
      node(id: $id) {
        id
        ...userFields
        __typename
      }
    }

    fragment userFields on User {
      name
      username
      isActive
    }
  `;

  const rootValue = {};
  const context = await getContext();
  const variables = {
    id: toGlobalId('User', user._id),
  };

  const result = await graphql(schema, query, rootValue, context, variables);
  expect(result.data!.node.id).toBe(variables.id);
  expect(result.data!.node.name).toBe(user.name);
  expect(result.data!.node.username).toBe(user.username);
  expect(result.data!.node.isActive).toBe(user.isActive);
  expect(result.data!.node.__typename).toBe('User');
});
```

This second test is just to check that our _node_ also works with spread fragment and has the same behavior as the first. However, the test fulfills its role of checking if it works for both types of query. 

## Next
Recently the relay team made a commit named [@refetchable support for @fetchable types](https://github.com/facebook/relay/commit/6ed719438829d02912c862407bbf84a6374f14f3) in which they add the `@fetchable` directive. It has the role to define the schema to specify that a GraphQL type is refetchable and what field should be used to refetch it. Therefore, this is a generalization of the pattern explained in this post. Instead _node_ on `QueryType` we must define _foo root field_ could implement refetchable on top of our custom _Foo Node Interface_. It's basically the _node_ interface with different field names.

## Conclusion
* Declare `globalIdField` on your GraphQL type to make GraphQL auto-generate a unique global _Node ID_ for your GraphQL object during creation and ensure that it must be refetched.
* 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 when a refetch provides the _Node ID_.
* Use _nodeInterface_ on your GraphQL type to instruct GraphQL to use the _nodeDefinitions_ implementation to resolve between _Node ID_ and actual server-side objects.
* Use the _registerTypeLoader_ to avoid the manual register on the `nodeRegistry` file and make the code more organized and scalable.
* Use tests to avoid unwanted behaviors.
* There was a generalization in the pattern. 

## Resources
* [GraphQL Global Object Identification Specification](https://relay.dev/graphql/objectidentification.htm).
* [Relay/GraphQL: De-mystifying Node Interface/ID](https://medium.com/code-oil/relay-graphql-de-mystifying-node-id-38757121b9c).
* [What is nodeInterface, nodeField and nodeDefinitions in Relay?](https://stackoverflow.com/questions/42576581/what-is-nodeinterface-nodefield-and-nodedefinitions-in-relay).
* [Relay Commits](https://github.com/facebook/relay/commits/master).