The magic of the Node interface
Tags: graphql, relay
Published: 2020-04-16 20:46:17

> This series of articles is written by [Gabriel Nordeborn](https://github.com/zth) and [Sean Grove](https://github.com/sgrove). Gabriel is a frontend developer and partner at the Swedish IT consultancy [Arizon](https://arizon.se), and has been a Relay user for a long time. Sean is a co-founder of [OneGraph.com](https://www.onegraph.com), unifying 3rd-party APIs with GraphQL.

The official GraphQL website recently added a section called [“Global Object Identification” to its collection of best practices](https://graphql.org/learn/global-object-identification/). In this article, we’ll dive into what global object identification is, why it’s useful and what type of developer experience it enables.

## What is Global Object Identification?

Global Object Identification in GraphQL is about two things:

1. Having each individual object in the graph be identifiable by a globally unique ID, *across types*. So no two objects can have the same ID, even if they're of different types.
2. Being able to query *any single object that has an* `id` only by that `id`. This is done via the `Node` interface which we’ll talk more in depth about below.

This enables libraries to safely automated away several things that are otherwise left up to the developer to implement and manage themselves:

- A GraphQL framework can automatically update the cache of any object since all objects have a unique ID.
- If we ever need to fetch *new* or *refreshed* fields on an object, all we need to know is its `GraphQL type` and its `id`.

We’ll go deeper into why these two points enable a great developer experience below. 

## What’s the `Node` interface anyway?

In schemas implementing the `Node` interface, you’ll see a top level field on the `Query` object called `node`, and it takes a single argument: `id`, and it returns a single field: `id` .


> Wat?

When you first look at it, it’s indeed a bit strange!
```graphql
    query NodeInterfaceExample1 {
      gitHub {
        node(id: "MDQ6VXNlcjM1Mjk2") {
          id # As one might expect, this is indeed "MDQ6VXNlcjM1Mjk2"
        }
      }
    }
```
But because `node` is an *interface*, it implements every GraphQL object in the schema that can be fetched via its `id`:
```graphql
    query NodeInterfaceExample2 {
      gitHub {
        node(id: "MDQ6VXNlcjM1Mjk2") {
          id
          ... on GitHubUser {
            login # "sgrove"
            followers {
              totalCount # it's over 9000!!!!
            }
          }
        }
      }
    }
```

## But why is it useful?

That means that if we know the *id* and the *GraphQL Type* of a thing, then we *always* know how to look it up. The alternative might be something like:
```graphql
     query WithoutInterfaceExample {
      gitHub {
        user(login: "sgrove") {
          login
          followers {
            totalCount
          }
        }
      }
    }
```
There are two challenges with this query that will mean lots of extra work for us as developers, but that means extra chances for us to get things wrong:

1. Finding a `GitHubUser` this way doesn’t accept its `id`, it only accepts a `login` argument. That means if we ever had, say, an `ownerId` value, we still need to know the `*login*` value. This gets trickier and trickier because every kind of object might be looked up by different keys.
2. Even if we *had* the `login` value, we have to build a potentially complicated query with nested selections (`gitHub.user` in this case) to get the extra fields we want.

Ultimately, both those bits of knowledge are implicit - we know them as developers, we have that knowledge in our head - and that means the computer and our tooling doesn’t know it.

But if our tooling *does* know how to look up any object given its `id`, then it can help us in wonderful ways. 

This will also relieve some pressure on the backend. Whenever you need to refetch a node that’s deeply nested in a query, the backend will need to resolve all levels above the node in order to get to it. If it’s able to just take the `id` and resolve a single node directly though, that added pressure goes away.

# Why globally unique ids? Aren’t integers good enough?

So with the `node` interface we can look up *any* object by its `id` - great! But that comes with an implication: No two objects in our entire API can have the same id!


> Quick aside: This *seems* daunting for just a moment at first, but there's an easy way to do this for any schema that we’ll see in just a moment

That means that every GraphQL object that has an `id` field (say, `User.id` or `Post.id`) must have a unique ID that *no other GraphQL Object has*. Take this query for example:

```graphql
    query FirstUserAndFirstPost {
      user {
        id
      }
      post {
        id
      }
    }
```
It’s common for lots of databases to use incrementing integers as ids, so you might expect a response like:
```json
    {
      "data": {
        "user": {
          "id": 1
        }
        "post": {
          "id": 1
        }
      }
    }
```
But combined with the `node` interface, this just won’t work. Say we have a post id and we want to get some additional fields (like its `title`). We *should* be able to run this query:

```graphql
    query NodeInterfaceExample3 {
      node(id: 1) {
        id
        ... on Post {
          title
        }
      }
    }
```
But there are *two* objects that could be looked up with the `id` of `1`! It’s now unclear if the object that will come back will be a `User` or a `Post`.

**Don’t lose heart! A simple solution is at hand!**

So if we want to *internally* keep using integers for our ID, how can we have globally unique IDs? Let’s look at the example ID used in the GitHub (whose API is beautiful and Relay compatible as well!) query: `node(id: "MDQ6VXNlcjM1Mjk2")`

```
    > atob("MDQ6VXNlcjM1Mjk2")
    "04:User35296"
```
Hey, I see some integers in there!

Basically, the technique is to construct a `nodeId`, and it looks like:
```javascript
    `base64Encode(${apiVersion}:${GraphQLObjectTypeName}${ObjectId})
```
So for our `post` and our `user` on version 1 of our API, we would expect to see:
```
    "MDE6UG9zdDE=" # decodes to "01:Post1"
    "MDE6VXNlcjE=" # decodes to "01:User1"
```
If we revisit our previous node example that had a conflict and use our new ID:

```graphql
    query NodeInterfaceExample4 {
      node(id: "MDE6UG9zdDE=") {
        id
        ... on Post {
          title
        }
      }
    }
```
Now our single `node` resolver can base64-decode the id, find that it’s looking for a `Post` object with `id` of `1`, and return the correct object.


> The reason we prefix the API version in the global id is to give us a chance to later change how we encode global ids in a non-breaking way, in case we need it. It’s a bit of flexibility we can buy for effectively nothing.

If things aren't quite clicking for you yet (especially if you don't get *why* an `id` would be anything but the database id of that object), it might help to think of the `id` field on a GraphQL type this way - you’re actually looking at a node in *GraphQL*, an entire graph, and not in your database table. This means that when you’re looking at the `id` of a GraphQL type, you’re looking at a node in a graph, not a table in a database. In this light, having the `id` of a GraphQL type be globally unique makes much more sense, since its primary objective is to be an identifier in your graph of objects, not in your database.

# What does this enable?

Like demonstrated above, most schemas will already have *some* way of resolving a single item of most things, even if it’s not via a top level `node` field. So why does this whole thing matter? Is it just for making the resolution slightly more convenient?

Well, that helps too of course. But the real power comes from *standardization*. 

If you think about it, even if your schema has a top level field called `postById(id: ID!)`, that indeed does resolve a post by an ID, there’s really no way for anyone but the schema creator to know that’s in fact what the field does. Without parsing the semantical meaning of `postById` *and* inferring that it in fact does mean “a single post, just by its ID”, any external tooling or framework will not be able to safely assume that that’s what that particular field does. You may have any number of fields resolving a single `Post`, like `postByDatabaseId(id: ID!): Post`, `mostLikedPost(id: ID!): Post`, `latestPost(id: ID!): Post` and so on. This makes it impossible for external tooling to safely assume which field it can use to refetch a single `Post`.

The `Node` interface, however, is an [official GraphQL best practice](https://graphql.org/learn/global-object-identification/), and this means that frameworks and tooling can build on top of it and anyone who implements it will benefit. And there’s some pretty cool things you can build with it. Let’s look at some concrete examples from Relay:

## Autogeneration of pagination queries

The latest iteration of Relay (now called only Relay, but commonly referred to as Relay Modern) can automatically generate queries for refetching or paginating data, since it can refetch any GraphQL object via the `Node` interface. This is enables a very ergonomic developer experience. Basically, Relay can take something like this:
```graphql
    fragment UserFriendsList_user on User {
      id
      friends(first: $first, after: $after) {
        edges {
          node {
            firstName
            lastName
          }
        }
      }
    }
```
And automatically generate a pagination query for that, like this:
```graphql
    query UserFriendsListPaginationQuery($id: ID!, $first: Int!, $after: String) {
      node(id: $id) {
        ... on User {
          id
          friends(first: $first, after: $after) {
            edges {
              node {
                firstName
                lastName
              }
            }
          }
        }
      }
    }
```
…just because it knows that `User`, the type the fragment `UserFriendsList_user` is on and where the pagination is defined, implements the `node` interface, and can therefore be *re-fetched* via its `id`. 

It doesn’t matter *where* the `User` whose friends we’re paginating is located in the query, the `node` interface let Relay refetch *any* `User`as long as it has its `id`.

We have a whole [article on pagination in Relay](https://dev.to/zth/pagination-with-minimal-effort-in-relay-gl4) you’re encouraged to check out here.

## Autogeneration of queries for your typical "Show more" functionality
In the same vein, Relay makes it really easy to build a classic "Show more" functionality. Relay can help you take something like this:

```graphql
    fragment ProfilePage_user on User {
      id
      name
      avatarUrl
      bio @include(if: $showMore)
    }
```

...and generate a query that'll let you fetch that fragment again, but now including `bio` by setting `$showMore` to true. 

Your experience of building the "show more" functionality would basically be as simple as running `refetch({showMore: true})` when the user presses the "Show more"-button. Relay takes care of the rest, and it can do that _because_ it knows how to refetch that `User` via its `id` using the `Node` interface, regardless of where that `User` is found.

## Wrapping up

Hopefully you’ve now got some insight into why globally unique IDs and the `Node` interface is a good idea. Relay make great use of it, and since it’s an official GraphQL best practice, any other tooling can build on top of it too.