Now to Interfaces…

Some Context

The GraphQL schema specification supports Interfaces and Union Types. An Interface is an abstract type that includes a certain set of fields that a type must include to implement the interface, while Union Types allow for the grouping of several types without the sharing of any structure.

For any non-trivial data structure, you will most probably need to leverage these constructs to model your data. The problem is:

Prisma does not support Interfaces or Union Types yet. There are open issues for each of them — see Interface and Union Type. graphql-yoga supports both of them but their usage is not yet documented, which makes it hard to actually implement anything. I opened an issue to know more a while back and this post is where it led me.

My approach

Since Prisma only supports Types and Enums at the moment, we have to find a way to model our data without using Interfaces in Prisma. We can however use Interfaces on the GraphQL server (graphql-yoga) so that the client facing API is properly structured and users get to request data across types using Inline Fragments.

This leaves us with 2 options:

Storing all data with optional type-specific fields under one type (the interface) in Prisma, and then splitting the data back between the primitive types in the app server. Storing the data in each primitive type on Prisma, and stitching things for queries on the app server.

The problem with option 2 is that you loose the consistency in pagination. How do you get the last 20 items for the interface? How many of each primitive type should you request? You could do 20, sort them, and take 20, but that seems inelegant to me.

So I picked option 1, let’s see how to implement it. I’ll give code snippets following the schema used in the docs.

Prisma Workaround

Basically, we want to merge all primitive types as a single “interface” type. Type-specific fields must be optional since they will not be available for every entry, and they are prefixed with the name of the primitive type to make sure they are unique. In the docs, we have:

Interface example from the GraphQL docs

Our workaround schema is:

A Prisma’s datamodel.graphql file with a mocked interface

Mapping interfaces in graphql-yoga

As desired, we declare in the schema for the client facing API the same interface and primitive types as in the docs. We also copy the schema of the dbCharacters query generated by Prisma as the characters query for our client facing API. This could probably be more refined. The return type is however changed to our interface, hence returned items should be mapped to a primitive type on which type-specific inline fragments can be used.

src/schema.graphql

In order to map items returned by Prisma to a primitive type, we need to provide a type resolver for our interface at the root of our resolvers object. I have separated the declaration of interface resolvers into a separate file and import it with object destructuring into the resolvers object. See the __resolveType example in the interfaces.js file. This is a simplistic example showcasing how to resolve types. You would implement yours according to the specific business logic of your data.

src/resolvers/index.js

src/resolvers/interfaces.js

The last thing to do is to implement the client API for the interface. It is backed by the corresponding API from Prisma but we need to translate I/Os between the 2 schemas. The resolver for the characters query is implemented in the Query.js file, which is pretty classic. The implementation details are as follow:

We must make sure all fields selected for the primitive types in the query are requested from Prisma. To do this I have written a utility function called makeSelection into interfaces.js which takes the info object from the resolver and parses the query AST ( GraphQLResolveInfo ) to generate the string selection sent to Prisma. This modifies the selection to make sure all fields nested in Inline Fragments such as ...on Droid { primaryFunction } will be queried from Prisma as normal prefixed fields, e.g. droid_primaryFunction . The code for this method was pretty much trial and error while inspecting the info object and mapping it to the expected selection to send to Prisma. Disclaimer: the code covers only the queries I have have been needing and might need additions to cover all use-cases. Note also that I’m not an expert with ASTs so there might be a better way to do this, please suggest in the comments if you know one. We must format the objects received from Prisma back to their expected form in the client API schema. I use another utility function called formatPrimitiveFields , also available in interfaces.js which takes a field such as droid_primaryFunction and remove the primitive type prefix.

src/resolvers/Query.js