Building a Relay GraphQL server with graphql-java

1.1k Views Asked by At

We're using graphql-java to build a graphql server and having trouble conforming to the Relay spec. According to the Relay spec, all nodes must be retrievable via a single query: node(id: ID). The graphql-java docs show support for Relay's node interface, however documentation for actually implementing this is rather sparse. The exact issue we are encountering is understanding how to generate a generic lookup for the node query? The Relay Todo example: https://github.com/graphql-java/todomvc-relay-java shows an extremely simple code-based approach with a single datafetcher, the example here never needs to "read" the nodes "type" or delegate that request to the correct dataFetcher.

schema.graphqls:

type Query {
    node(id: ID!): Node
    user(id: ID!): User
    otherType(id:ID!): OtherType
}

interface Node {
   id: ID!
}

type User implements Node {
   id: ID!
   firstName: String
   lastName: String
}

type OtherType implements Node {
   id: ID!
   description: String
   stuff: String
}

We're currently using the SDL to generate our schema (as shown below)

@Bean
private GraphQLSchema schema() {
     Url url = Resources.getResource("schema.graphqls");
     String sdl = Resources.toString(url, Charsets.UTF_8);
     GraphQLSchema graphQLSchema = buildSchema(sdl);
     this.graphQL = GraphQL.newGraphQL(graphQLSchema).build();
     return graphQLSchema;
}
private GraphQLSchema buildSchema(String sdl) {
      TypeDefinitionRegistry typeRegistry - new SchemaParser().parse(sdl);
      RuntimeWiring runtimeWiring = buildWiring();
      SchemaGenerator schemaGenerator = new SchemaGenerator();
      return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring);
}
private RuntimeWiring buildWiring(){
    return RuntimeWiring.newRuntimeWiring()
    .type(newTypeWiring("Query")
          .dataFetcher("user", userDispatcher.getUserById()))
    .type(newTypeWiring("Query")
          .dataFetcher("otherType", otherTypeDispatcher.getOtherTypeById()))
    .type(newTypeWiring("Query")
          .dataFetcher("node", /*How do we implement this data fetcher? */))
    .build()
}

Assumably, we would have to wire up a datafetcher as shown in the last line above. However, this is where the documentation starts to get hazy. Does the above conform to the Relay spec? How would we go about implementing a single node dataFetcher which returns back a reference to any single node, regardless of the underlying type (User, OtherType, etc)?

2

There are 2 best solutions below

0
On

The Node interface in the schema needs a TypeResolver and Data Fetcher Implementation.

Even if you provide a data fetcher implementation and wire it up as suggested by AllirionX, you still need to provide a resolver for the Node interface to help graphql-java understand which concrete object type it should resolve.

.type(newTypeWiring("Query").dataFetcher("node", new NodeDataFetcher()))

Something like this -

.type(newTypeWiring("Node").typeResolver(new TypeResolver() {
                @Override
                public GraphQLObjectType getType(TypeResolutionEnvironment env) {
                    return null;
                }
            }))
0
On

You are right, you need to implement a node data fetcher that will return a Node from a Node id.

I am going to assume you are using an underlying sql database: a Node could be of any type, and that means you cannot use the database id as a Node id (because two nodes of different types could have the same database id). You need to build your own Node id.

A simple way to build such an id is to concatenate the object type and the object id. Example: "User:4234".

As the relay graphql server specification specify:

The IDs we got back were base64 strings. IDs are designed to be opaque (the only thing that should be passed to the id argument on node is the unaltered result of querying id on some object in the system), and base64ing a string is a useful convention in GraphQL to remind viewers that the string is an opaque identifier.

We need to encode/decode the node id to base 64.

The code below has not been tested, but will give you an idea on how to proceed.

DataFetcher dataFetcher = new DataFetcher() {
            
  @Override
  Object get(DataFetchingEnvironment environment) {
    Object argument = environment.getArgument("id");
    if(argument instanceof String) {
      String node = (String) argument;
      //TODO decode base 64 nodeId
      //TODO split nodeId into String `nodeType` and Long `id`
      //TODO retrieve your object of type `nodeType` and id `id`
      
    }
  }
}