9

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...

3
  • resolver for trivia returns an object (from db) but it doesn't contain rounds property ... and no resolver to return value (array of objects) for this Commented Jun 3, 2020 at 23:49
  • @xadm The object returned from trivia does have the rounds array with the rounds in them. Commented Jun 3, 2020 at 23:57
  • @xadm thanks for the tip. I'm coming at this completely new, trying to wrap my head around GraphQL. I tried union stuff, didn't quite work. I just don't know how to do it. Oh well. Commented Jun 4, 2020 at 0:03

1 Answer 1

23

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.

Sign up to request clarification or add additional context in comments.

6 Comments

Thanks for the in-depth response! I really appreciate it. Only thing, I have no idea where to insert or execute the resolver function... I'm using graphql-tools and not apollo, if that helps.
If you're using makeExecutableSchema, then you're passing a resolvers option to it. That resolvers object should look as shown above (or in the docs).
I'm using addResolversToSchema, and the resolver would make it return the string "MultipleChoice" for example, and not actually have any data. I'm so lost. This is too confusing.
I would use makeExecutableSchema as shown in the graphql-tools docs here. Then modify the resolvers object to include the Round.__resolveType function as I've shown.
One of the best written answers I've read in a long time.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.