GraphQL Error: Expected undefined to be a GraphQL type

GraphQL Error: Expected undefined to be a GraphQL type


2

I have been running into this GraphQL error for the past day or so and I have a hard time pinpointing where the problem lies. I hope the following makes sense…

In my project I have the following GraphQL types; Questionnaire & Question were a Questionnaire has many Questions and a Question belongs to a single Questionnaire.

I have the following queries: getQuestionnaires (gets all Questionnaires), getQuestionnaire (gets a single Questionnaire by its Id), getQuestions (gets all Questions, either all questions in the system or the questions for a Questionnaire by specifying the questionnaireId).

This is the Questionnaire Type (I have left out a bunch of attributes for brevity):

import {
  GraphQLID,
  GraphQLObjectType,
  GraphQLNonNull,
} from 'graphql'
import QuestionType from './Question'
import QuestionResolver from '../resolver/question'

const QuestionnaireType: GraphQLObjectType = new GraphQLObjectType({
  name: 'Questionnaire',
  fields() {
    return {
      id: {
        type: new GraphQLNonNull(GraphQLID),
      },

      // other literal attributes go here

      questions: QuestionResolver.query.getQuestions,
    }
  },
})

export default QuestionnaireType

As you can see, the questions attribute for the Questionnaire Type simply calls the resolver for getQuestions.

This is the resolver for getQuestions:

import {
  GraphQLList,
  GraphQLID
} from 'graphql'

import QuestionType from '../../type/Question'

export default {
  type: GraphQLList(QuestionType),
  description: 'Returns a list of questions',
  args: {
    questionnaireId: {
      type: GraphQLID,
      description: 'Questions that belong to this questionnaire',
    },
  },
  async resolve(source: any, { questionnaireId = undefined }: any, ctx: any): Promise<any> {
    // Based on the `questionnaireId` get the questions. If `undefined` get "all" questions.
  },
}

As you can see, the getQuestions returns a GraphQLList(QuestionType). Which brings us to the Question Type:

import {
  GraphQLID,
  GraphQLObjectType,
  GraphQLNonNull,
} from 'graphql'
import QuestionnaireType from './Questionnaire'
import QuestionnaireResolver from '../resolver/questionnaire'

const QuestionType: GraphQLObjectType = new GraphQLObjectType({
  name: 'Question',
  fields() {
    return {
      id: {
        type: new GraphQLNonNull(GraphQLID),
      },

      questionnaire: QuestionnaireResolver.query.getQuestionnaire,
    }
  },
})

export default QuestionType

Okay. So far so good. This code compiles and runs.

However, there is a problem with this code. The getQuestions resolver takes in the Questionnaire Id as a parameter (questionnaireId). If the Questionnaire Id is not passed (undefined) it simply returns all Questions in the system. This means that in the Questionnaire Type, on the questions attribute, we don’t want call the getQuestions resolver directly, but we want to wrap it in a resolver that extracts the source.id (the source is the Questionnaire in this context) and pass it as an argument to the getQuestions resolver. If we don’t do this, then querying for questions on a Questionnaire will always return all Questions in the system, which is what we don’t want.

So, we wrap the resolver in a resolver that extracts the Questionnaire Id before calling the getQuestions resolver. Our Questionnaire Type now looks like this:

import {
  GraphQLID,
  GraphQLObjectType,
  GraphQLNonNull,
} from 'graphql'
import QuestionType from './Question'
import QuestionResolver from '../resolver/question'

const QuestionnaireType: GraphQLObjectType = new GraphQLObjectType({
  name: 'Questionnaire',
  fields() {
    return {
      id: {
        type: new GraphQLNonNull(GraphQLID),
      },

      // other literal attributes go here

      questions: {
        type: GraphQLList(QuestionType),
        async resolve(source: any, args: any, ctx: any): Promise<any> {
          return QuestionResolver.query.getQuestions.resolve(
            source,
            {
              questionnaireId: source.questionnaireId,
            },
            ctx
          )
        },
      },
    }
  },
})

export default QuestionnaireType

We have now wrapped the getQuestions resolver to extract the source.id (i.e. the Questionnaire Id) so we can always pass it on to the getQuestions resolver (since that is what we always want in this context).

However, when I do the above. I get this error:

[Node] /Project/api/node_modules/graphql/type/definition.js:91
[Node]     throw new Error("Expected ".concat((0, _inspect.default)(type), " to be a GraphQL type."));
[Node]     ^
[Node]
[Node] Error: Expected undefined to be a GraphQL type.
[Node]     at assertType (/Project/api/node_modules/graphql/type/definition.js:91:11)
[Node]     at new GraphQLList (/Project/api/node_modules/graphql/type/definition.js:307:19)
[Node]     at Object.GraphQLList (/Project/api/node_modules/graphql/type/definition.js:309:12)
[Node]     at Object.<anonymous> (/Project/api/build/graphql/resolver/question/getQuestions.js:11:21)
[Node]     at Module._compile (internal/modules/cjs/loader.js:936:30)
[Node]     at Object.Module._extensions..js (internal/modules/cjs/loader.js:947:10)
[Node]     at Module.load (internal/modules/cjs/loader.js:790:32)
[Node]     at Function.Module._load (internal/modules/cjs/loader.js:703:12)
[Node]     at Module.require (internal/modules/cjs/loader.js:830:19)
[Node]     at require (internal/modules/cjs/helpers.js:68:18)

I’m stumped….

On line 11 of getQuestions is:

08: export default {
09:   type: GraphQLList(QuestionType),
10:   description: 'Returns a list of questions',
11:   args: {
12:     questionnaireId: {
13:       type: GraphQLID,
14:       description: 'Questions that belong to this questionnaire',
15:     },

I think I have literally tried everything that comes to mind to find the problem. My project is biggish and I use this technique in several other places without problems but for some reason, in this particular scenario it fails to compile..

The only workaround I have at the moment is to use something like this in the getQuestions resolver:

import {
  GraphQLList,
  GraphQLID
} from 'graphql'

import QuestionType from '../../type/Question'

export default {
  type: GraphQLList(QuestionType),
  description: 'Returns a list of questions',
  args: {
    questionnaireId: {
      type: GraphQLID,
      description: 'Questions that belong to this questionnaire',
    },
  },
  async resolve(source: any, { questionnaireId = undefined }: any, ctx: any): Promise<any> {

    // The workaround
    const id = questionnaireId || source.id
  },
}

However, it’s not the responsibility of the getQuestions resolver to look at the source to extract arguments and also, the source could technically be another object than a Questionnaire adding more complexity to the getQuestions resolver.

Any help and insight appreciated.

2 Answers
2


3

You need to look at the compiled code to trace the error. Based on the stack trace, though, it looks like QuestionType is evaluating to undefined. This can happen when you have circular dependencies (i.e. module A which imports module B which imports module A etc.).

You should generally avoid calling other resolvers inside a resolver to begin with. If you have a lot of duplication, the shared logic can be extracted to a common function that can be called from both resolvers. There’s a tendency for this to happen when our resolvers contain business logic — it’s a good practice to try to keep your resolvers as lean as possible, isolating business logic inside separate models or services whose methods can then be called from your resolvers.

It’s hard to know how to fix your circular dependency without seeing all the relevant code. The guaranteed way to resolve any circular dependencies is to swap out your import statements for dynamic requires inside the fields function. However, you may not find that solution particularly elegant. A good first step may be to consolidate some of your modules — for example, creating a single Question module that includes the Question type and all related Query and Mutation fields.


0

@Daniel Rearden’s answer worked for me: inline require.

But my problem was that I had circular dependency on both files not just the one I was requiring on.



Leave a Reply

Your email address will not be published. Required fields are marked *