Support for Relay Modern

Thanks for the reply! I was able to get most of the basics (useQueryLoader, usePreloadedQuery and useFragment) working (note this is their upcoming API). I haven’t tried with their current API (QueryRenderer etc).

Since relay is a popular framework, do you think this will receive proper support / implementation documentation?

1 Like

As per Relay’s GraphQL server specification:

The three core assumptions that Relay makes about a GraphQL server are that it provides:

  1. A mechanism for refetching an object.
  2. A description of how to page through connections.
  3. Structure around mutations to make them predictable.

Assumption-1

It requires the following query to be supported by the GraphQL server:

type Query {
   node(id: ID!): Node
}

At present, Dgraph doesn’t enforce a Node interface on the user-defined types. User can choose to define their own Node interface, but that would generate the following query:

type Query {
   getNode(id: ID!): Node
}

Note that it is getNode, and not node although the behavior of these two is the same.

Assumption-2

It strictly requires the cursor-based pagination model described here: GraphQL Cursor Connections Specification
Dgraph’s GraphQL API doesn’t support that model at present. It supports the first, offset based model as in this example:

type Message { ... }

type Query {
   queryMessage(filter: MessageFilter, order: MessageOrder, first: Int, offset: Int): [Message]
}

Also, that model requires nested object lists to be handled with a level of indirection as edges to fit the PageInfo objects. That requires a full rework of how the GraphQL API is generated by Dgraph.

Assumption-3

This is satisfied in Dgraph’s current GraphQL API. No changes required here.

TL;DR

At present, Dgraph generates a free-form GraphQL API, which can be used easily with GraphQL clients like Apollo.
In order to support the Relay framework, it requires a complete rework.
So, a nice idea would be to support two endpoints that serve GraphQL in different forms:

  • /graphql: This is the current endpoint, which serves the free-form GraphQL API.
  • /graphql-relay: A new endpoint to support a GraphQL API that respects the constraints laid down by relay.

GIven that relay is a popular framework, we should be supporting this. But, also given that our current GraphQL API is under fast development as of now, I would not advocate having that support right now. It would be better that once we have a stable GraphQL API, then only we should consider adding support for relay.

So, yes it will receive support, but I don’t expect that to be happening very soon.

Thanks

cc: @vvbalaji

4 Likes

Thanks @abhimanyusinghgaur for the update! You may have noticed that Relay just released their next major version that uses hooks – an API that encourages a “render-as-you-fetch” pattern (which is inline with the upcoming React Suspense patterns). This new API is much easier to understand and implement so I’d imagine it will bring new front end devs to explore Relay.

The suggestion of two endpoints makes total sense, it is how I noticed Hasura solved this problem (/graphql and /relay).

I have been really enjoying using Dgraph and having it compatible with Relay would make for a great combo. Where is the best place for me to check in around when this will be supported? The blog?

2 Likes

Probably this post. Or keep an eye on the release announcements here on discuss.

Just checking in to see if there are any updates for supporting Relay :slightly_smiling_face:

1 Like

@abhimanyusinghgaur just checking in to see if there was any progress with this?

So it looks like, for the moment - you shouldn’t use dgraph if you want to build a Relay-compliant backend. Probably hasura & a traditional db would be the right solution.
Correct me if I’m mistaken.

Hi!

I know this is a little old but I found a (admittedly very questionable) workaround for Assumption 1 (also part of the GraphQL specs) that a server must provide a root field called node which returns an interface Node. Doing this, will check 1 and 3 of the Relay compliance and pagination can also be done via first/next.

At first we have to add the Node interface to our schema and define a custom lambda resolver called node.

Node interface

interface Node @generate(
    query: {
        get: true,
        query: false,
        aggregate: false
    },
    mutation: {
        add: false,
        delete: false
    },
    subscription: false
) {
  id: ID!
}

I have disabled the generation of all queries/mutations apart from getNode.

Apply to a type

type Test implements Node {
  name: String
  ...
}

Apply the Node interface to any type you wish to query via node (@refetchable will work when using Relay).

Set up lambda

type Query {
  node(id: ID!): Node @lambda
}

So far so good, we have set all up but unfortunately the query node will not work since we would have to implement all possible query solutions which is offered by getNode. In order to do so I alter the id string of the input parameter and attach the query which gets generated by Relay (I know this is …).

I use react-relay-network-modern for setting up the Relay environment but it should work with the standard implementation as well. So when defining my network, I do:

const network = new RelayNetworkLayer([
    cacheMiddleware({
      size: 100, // max 100 requests
      ttl: 900000, // 15 minutes
    }),
    urlMiddleware({
      url: (req) => Promise.resolve(GRAPHQL_ENDPOINT),
    }),
    (next) => async (req) => {
      let queryBody = {
        query: req.getQueryString(),
        variables: req.getVariables(),
      };

      // THIS IS WHERE THE CRAZY HAPPENS
      if ( queryBody.query.includes('node(id: $id)') ) {
         queryBody.variables.id = queryBody.variables.id + '_QUERY_' + queryBody.query;
      }
 
      req.fetchOpts.body = JSON.stringify(queryBody);
      const res = await next(req);
      return res;
    },
  ]);

In the lambda I can now parse the string, replace node(id: $id) with getNode(id: $id) and run the query.

The lambda

export const getNode = async ({ args, graphql }) => {
  const { id } = args;
  const queryBody = id.split('_QUERY_');
  const queryId = queryBody[0];
  const query = query[1].replace('node(id: $id)', 'getNode(id: $id)');

  const res = await this.graphql( query, { id: queryId });
  return res.data.getNode;
}

I know this is a horrendous workaround but it works and lets me do @refetchables as well. I guess it would not be a major task for the Dgraph Team to automatically add the Node Interface and rename the getNode query to node. We would still have to implement Node to every type we would need to have access but that’s ok I guess.

Let me know what you think!

2 Likes

During React’s official 2021 conference they re-introduced Relay as the data fetching framework you should be using for GraphQL.

Interesting talk that is worth the watch! Hopefully this might inspire getting Dgraph GraphQL schema to be Relay compliant :laughing:

1 Like

Am I right to understand that a full Relay compatibility was never achieved?

As far as I’m aware, the answer is no.

However, as stated by @abhimanyusinghgaur, Dgraph fulfils Spec 3 out of the box. Spec 1 can be achieved by my horrendous workaround. Only cursor based pagination (Spec 2) is missing to be fully compliant and this seems to require a full rework of Dgraph’s generation of the GraphQL API.

There might be a possibility of extending the schema in a way to achieve cursor based pagination but this certainly results in quite an effort. The only way I could imagine achieving this is to somehow utilise the Node interface for this - eg. by assigning generalised edges to all types in your schema (or at least the types which should be paginated). Then you’d need to extend type Query and build custom resolver, maybe similar to @huntersofbook/relay-cursor-paging or use cursor_to_offset from graphql/relay.

However, I’ve not tried any of this since so far we can live with the offset based pagination provided by Dgraph.