Relay Node Interface

In this section, we continue the GraphQL example by explaining how to implement the Relay Node Interface. If you’re not familiar with the Node interface, read the following paragraphs that were taken from relay.dev:

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. In the response, the schema provides a standard way of providing these IDs.

We refer to objects with identifiers as “nodes”. An example of both of those is the following query:

  1. {
  2. node(id: "4") {
  3. id
  4. ... on User {
  5. name
  6. }
  7. }
  8. }

Clone the code (optional)

The code for this tutorial is available under github.com/a8m/ent-graphql-example, and tagged (using Git) in each step. If you want to skip the basic setup and start with the initial version of the GraphQL server, you can clone the repository as follows:

  1. git clone git@github.com:a8m/ent-graphql-example.git
  2. cd ent-graphql-example
  3. go run ./cmd/todo/

Implementation

Ent supports the Node interface through its GraphQL integration. By following a few simple steps you can add support for it in your application. We start by telling gqlgen that Ent provides the Node interface by editing the gqlgen.yaml file as follows:

gqlgen.yml

  1. # This section declares type mapping between the GraphQL and Go type systems.
  2. models:
  3. # Defines the ID field as Go 'int'.
  4. ID:
  5. model:
  6. - github.com/99designs/gqlgen/graphql.IntID
  7. Node:
  8. model:
  9. - todo/ent.Noder

To apply these changes, we rerun the code generation:

  1. go generate .

Like before, we need to implement the GraphQL resolvers in ent.resolvers.go. With a one-liner change, we can implement those by replacing the generated gqlgen code with the following:

ent.resolvers.go

  1. func (r *queryResolver) Node(ctx context.Context, id int) (ent.Noder, error) {
  2. - panic(fmt.Errorf("not implemented: Node - node"))
  3. + return r.client.Noder(ctx, id)
  4. }
  5. func (r *queryResolver) Nodes(ctx context.Context, ids []int) ([]ent.Noder, error) {
  6. - panic(fmt.Errorf("not implemented: Nodes - nodes"))
  7. + return r.client.Noders(ctx, ids)
  8. }

Query Nodes

Now, we’re ready to test our new GraphQL resolvers. Let’s start with creating a few todo items by running this query multiple times (changing variables is optional):

  1. mutation CreateTodo($input: CreateTodoInput!) {
  2. createTodo(input: $input) {
  3. id
  4. text
  5. createdAt
  6. priority
  7. parent {
  8. id
  9. }
  10. }
  11. }
  12. # Query Variables: { "input": { "text":"Create GraphQL Example", "status": "IN_PROGRESS", "priority": 1 } }
  13. # Output: { "data": { "createTodo": { "id": "2", "text": "Create GraphQL Example", "createdAt": "2021-03-10T15:02:18+02:00", "priority": 1, "parent": null } } }

Running the Node API on one of the todo items will return:

  1. query {
  2. node(id: 1) {
  3. id
  4. ... on Todo {
  5. text
  6. }
  7. }
  8. }
  9. # Output: { "data": { "node": { "id": "1", "text": "Create GraphQL Example" } } }

Running the Nodes API on one of the todo items will return:

  1. query {
  2. nodes(ids: [1, 2]) {
  3. id
  4. ... on Todo {
  5. text
  6. }
  7. }
  8. }
  9. # Output: { "data": { "nodes": [ { "id": "1", "text": "Create GraphQL Example" }, { "id": "2", "text": "Create Tracing Example" } ] } }

Well done! As you can see, by changing a few lines of code our application now implements the Relay Node Interface. In the next section, we will show how to implement the Relay Cursor Connections spec using Ent, which is very useful if we want our application to support slicing and pagination of query results.