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