How to add a `resolveType` to GraphQL?

Viewed 7666

I am trying to query a single MongoDB document (trivia) using GraphQL, but am having trouble with one of the document fields. It's the trivia.rounds field that should return an array of objects (either LightningRound or MultipleChoiceRound).

schema.graphql

type Trivia {
  _id: String!
  createdAt: String!
  rounds: [Round]!
}

interface Round {
  type: String!
  theme: String!
  pointValue: Int!
}

type LightningRound implements Round {
  type: String!
  theme: String!
  pointValue: Int!
  questions: [LightningRoundQuestion]
}

type MultipleChoiceRound implements Round {
  type: String!
  theme: String!
  pointValue: Int!
  questions: [MultipleChoiceRoundQuestion]
}

// ...

trivia.js // resolver

require('dotenv').config()
const { ObjectId } = require('mongodb')

const trivia = (app) => {
  return async (root, { _id }) => {
    return app
      .get('db')
      .collection(process.env.DB_COLLECTION_TRIVIA)
      .findOne(ObjectId(_id))
  }
}

module.exports = {
  trivia
}

graphql query

query {
  trivia(_id: "5e827a4e1c9d4400009fea32") {
    _id
    createdAt
    rounds {
      __typename
      ... on MultipleChoiceRound {
        type
        theme
        }
            ... on PictureRound {
        type
        theme
        }
            ... on LightningRound {
        type
        theme
        }
    }
  }
}

I keep getting the error:

"message": "Abstract type \"Round\" must resolve to an Object type at runtime for field \"Trivia.rounds\" with value { questions: [[Object], [Object]] }, received \"undefined\". Either the \"Round\" type should provide a \"resolveType\" function or each possible type should provide an \"isTypeOf\" function."

I don't understand what it means by resolveType or isTypeOf. I've seen this in other questions, but have no clue what to implement in my setup. The db connection and resolver works fine if I remove the rounds field, so it's something there...

1 Answers

GraphQL supports two kinds of abstract types -- unions and interfaces. An abstract type is a type that represents two or more possible types. Abstract types allow you to specify a single type for your field that could be one of several possible types at runtime (i.e. when the query is executed). When executing a query, GraphQL can never return an abstract type -- instead, the type has to be resolved into one of the possible types when the query is executed.

If a field returns a list, then the type for each item in the list will resolved separately. This type resolution happens before any of the fields on each item are resolved. More to the point, the type that's resolved determines which fields need to be resolved in the first place.

In your example above, you've defined an abstract type (the interface Round) and several possible types for it (LightningRound, MultipleChoiceRound, etc.). However, you have not told GraphQL how to determine whether a Round is a LightningRound, a MultipleChoiceRound or another possible type. This is the purpose of providing a resolveType function. You typically define a resolveType function for each abstract type in your schema. Assuming you're using graphql-tools or apollo-server, you provide this function through the same resolver map object you use to define your resolvers:

const resolvers = {
  Round: {
    __resolveType: (round) => {
      // your code here
    },
  },
}

resolveType will be passed the Round object (i.e. one of the objects returned by your rounds resolver) -- you can use that value to determine what kind of Round it is. Based on your code, I'm guessing you'd use the type property to differentiate between the different types. resolveType should return a string value with the name of the matched type. So it could be as simple as:

const resolvers = {
  Round: {
    __resolveType: (round) => {
      return round.type
    },
  },
}

For additional examples, see the docs.

isTypeOf is an alternative approach to resolving the type. Instead of defining a resolveType function for the abstract type, you can define a isTypeOf function for each possible type. This function returns true or false to indicate whether the object it received is in fact the type. There are uses for isTypeOf, but it's typically easier to just use resolveType instead.

Related