DEV Community

Marais Rossouw
Marais Rossouw

Posted on

@fetchable with Relay

How can I use Relay, but my backend doesn't support the Node interface? Great question, see like many companies or individuals out there wanting to leverage the power that comes with Relay, but soon realize that some of it's features cannot be used such as usePaginationFragment or @refetchable.

For a long time we Relay had introduced and used a Query field called node. This was used to effectively get any "node" from your graph of entities. Allowing for a more granular way to refetch (or update) an existing item on the screen, or to not have to drill down multiple nested fields to get back one thing, which in most cases is huge for performance depending on how your server is built.

This was leveraged by the usePaginationFragment hook, making it super easy to paginate a collection data, whereby the initial data was given to it, but needed to be "re-fetched" to get subsequent pages. This mean that you could have one page level query that got everything for the page, but then just re-fetching the list of comments, for example.

Now, this meant that your server needed to support the Node interface and node field. Which is well and good if you're starting on a greenfield server — but to others this is a daunting task.

Introducing the @fetchable SDL directive

This directive aims to solve the situation allowing you to progressive enhance your schema to make use of it's feature set without having to change too many things.

Its defined like this:

directive @fetchable(field_name: String!) on OBJECT
Enter fullscreen mode Exit fullscreen mode

note here this is a directive you need to make available on the SDL not on your consumer queries.

along with this you also need to introduce a field__<typename>(<name>: ID!): <typename> Query field used to "re-fetch" that type.

so let's say you had this existing schema:

type Foo {
  databaseId: ID!
  name: String!
  baz: Int!
}

type Query {
  foo(name: String): Foo
}
Enter fullscreen mode Exit fullscreen mode

for simplicity sake, just assume this type Foo was deeply nested somewhere super awkward to just query from the root.

and now you're wanting to refetch this, maybe for polling, or for pagination or what have you.

We simply need to introduce a new field, a directive and everything in the system should just work.

type Foo @fetchable(field_name: "databaseId") {
  databaseId: ID!
  name: String!
  baz: Int!
}

type Query {
  foo(name: String): Foo
  fetch__Foo(databaseId: ID!): Foo
}
Enter fullscreen mode Exit fullscreen mode

and BOOM, we can now get back any Foo from any level, without needing to navigate a tree of fields.

Just to point this out as well. The server doesn't need to do anything special about that directive. It just has to implement the fetch field, and that's it. The relay-compiler will compile away that directive.


Just to quickly illustrate how this would look like with the node field.

interface Node {
  id: ID!
}

type Foo implements Node {
  id: ID!
  databaseId: ID!
  name: String!
  baz: Int!
}

type Query {
  foo(name: String): Foo
  node(id: ID!): Node
}
Enter fullscreen mode Exit fullscreen mode

as you can see, we needed to add an interface make our type now extend that and add all the logic's around that new id field. This is still the prefers approach, as it would mean you'd end up with an extremely cluttered root query field as your field gamut grows.

Top comments (0)