Graphql-js: What is the purpose of resolveType in Interface?

Created on 23 May 2017  路  6Comments  路  Source: graphql/graphql-js

I'm sorry if it's obvious to all but for sure not for me.

After learning GraphQL official docs a lot, StackOverflow issues and GraphQL packages code I've not found the real explanation of importance resolveType and isTypeOf in GraphQL. Following best practices I've forced to use interfaces in two variants:

  • I can define resolveType in Interface Type
const nodeInterface = new GraphQLInterfaceType({
  name: 'NodeInterface',
  fields: {
    id: { type: new GraphQLNonNull(GraphQLID) },
  },
  resolveType: (data) => {
    if (data.name) return tagType;
    if (data.email) return userType;
    // And so on. But how return proper Type if my Tag Type is similar to Category Type? That's the reason why I prefer the second way and use isTypeOf
    return null;
  }
});
  • I can define isTypeOf property in every Type which implementing Interface Type
const tagType = new GraphQLObjectType({
  name: 'Tag',
  description: 'Tag of the post',
  fields: () => ({
    id: {
      type: new GraphQLNonNull(GraphQLID),
      description: 'ID of the tag',
    },
    name: {
      type: new GraphQLNonNull(GraphQLString),
      description: 'Name of the tag',
    },
    slug: {
      type: GraphQLString,
      description: 'Slug of the tag',
    },
  }),
  isTypeOf: value => value instanceof Tag, // Below another examples I was faced while digging
  // isTypeOf: value => !!value.name
  // isTypeOf: value => true // This is really funny one
  interfaces: [nodeInterface],
});

But what is the purpose of this? If I using MongoDB it's just bloating my code as another rule to follow but without fully understanding why?

Most helpful comment

First, to answer your primary question, this function allows GraphQL to determine what type is being used at runtime. This is important because at runtime if you declared a field of the interface type Node, then at runtime it needs to determine if it is the type Tag or of the type User so it knows how to proceed.

More specifically, GraphQL.js allows you to supply two methods (isTypeOf() or resolveType()) so you can use one of two common patterns to do this. You do not need to do both, you can choose which one works best for your codebase.

First and most common is resolveType(), this is named for its responsibility for doing exactly what I described above. Given a value, you need to return which Object type represents that value.

However because Interfaces are often broadly used, maintainability of a resolveType() method can become difficult if there are very many implementing Object types for that Interface. Ideally when adding a new Object type that implements an Interface, you don't need to edit the code for the Interface type as well. The isTypeOf() method allows you to do this. This is possible because resolveType() has a default implementation which looks at all implementing types, and calls isTypeOf() on each until one returns true.

So to recap, you must define at least one of these two methods so that GraphQL knows how to resolve an Interface type into an Object type at runtime, however you do not need to provide both.

All 6 comments

First, to answer your primary question, this function allows GraphQL to determine what type is being used at runtime. This is important because at runtime if you declared a field of the interface type Node, then at runtime it needs to determine if it is the type Tag or of the type User so it knows how to proceed.

More specifically, GraphQL.js allows you to supply two methods (isTypeOf() or resolveType()) so you can use one of two common patterns to do this. You do not need to do both, you can choose which one works best for your codebase.

First and most common is resolveType(), this is named for its responsibility for doing exactly what I described above. Given a value, you need to return which Object type represents that value.

However because Interfaces are often broadly used, maintainability of a resolveType() method can become difficult if there are very many implementing Object types for that Interface. Ideally when adding a new Object type that implements an Interface, you don't need to edit the code for the Interface type as well. The isTypeOf() method allows you to do this. This is possible because resolveType() has a default implementation which looks at all implementing types, and calls isTypeOf() on each until one returns true.

So to recap, you must define at least one of these two methods so that GraphQL knows how to resolve an Interface type into an Object type at runtime, however you do not need to provide both.

@leebyron Thanks for it, Lee. It's almost exactly what I need. But if each type return its own type in the isTypeOf() why this function has no default behavior? It can be convenient only to change isTypeOf if it returns another type instead own.

I麓m trying the following:

import { 
    GraphQLInterfaceType,
    GraphQLNonNull, 
    GraphQLID 
} from 'graphql';


const NodeInterface = new GraphQLInterfaceType({
    name: 'Node',
    fields: {
        id: {
            type: new GraphQLNonNull(GraphQLID)
        }
    }
});

export default NodeInterface;
 import { 
    GraphQLObjectType, 
    GraphQLInputObjectType,
    GraphQLNonNull,
    GraphQLID,
    GraphQLList,
    GraphQLString, 
    GraphQLInt, 
    GraphQLBoolean 
} from 'graphql';

 import NodeInterface from '../interfaces';

 import UserModel from '../../models/User';

 const fields = {
    id: {
        type: new GraphQLNonNull(GraphQLID)
    },
    name: {
        type: GraphQLString
    },
    users: {
        type: new GraphQLList(UserType),
        resolve(company) {
            const { _id } = company;
            return UserModel.find({ companyId: _id }).exec();
        }
    }
 };

 export const CompanyType = new GraphQLObjectType({
    name: 'Company',
    description: 'Company',
    interfaces: [NodeInterface],
    isTypeOf: value => value instanceof CompanyType, <<<< ERROR HERE
    fields: fields
 })

I麓m getting the following error at isTypeOf:
Right-hand side of 'instanceof' is not callable

I cannot find a detailed documentation for isTypeOf featura and I don麓t understand why is it not working in my case.... Help appreciated.

@renatonmendes - you should back up and make sure isTypeOf: value => value instanceof CompanyType is testing the thing you think it is.

The error you're seeing is because CompanyType is not a class from which things can be instances - it's defined immediately above where you're using it as a description of a GraphQL type. Values are definitely not going to be instances of that.

isTypeOf should return true if the runtime value is to be represented by this GraphQL type. That's going to depend on where these values are coming from on your system.

Do you have some underlying ORM where you are actually doing something like new Company() to construct these? If so, then value instanceof Company might be appropriate. Are these values that come from a database? Then perhaps there's a field in your database called _type which you can check the value of? Or perhaps there are distinguishing fields that objects of type Company have that no other type has that you can check for?

What this looks like for you will depend on the underlying system you're exposing with GraphQL

Humm. I麓m a bit confused about timing here. Let麓s take the following node query scenario:

query {
  node(_id:"598360ab8713621aac426e88") {
    ... on Company {
        _id
   }
  }
}

With the code below:

const RootQuery = new GraphQLObjectType({
    name: 'RootQuery',
    description: 'The root query',
    fields: {
        viewer: {
            type: NodeInterface,
            resolve(source, args, context) {
                return { result: "VIEWER!" };
            }
        },
        node: {
            type: NodeInterface,
            args: {
                _id: {
                    type: new GraphQLNonNull(GraphQLID)
                }
            },
            resolve(source, args, context, info) {
                return { result: "NODE QUERY" };
            }
        },
        ...CompanyQueries,
        ...UserQueries
    }
});

const schema = new GraphQLSchema({
    types: types, (Array of types: CompanyType, UserType, etc.)
    query: RootQuery,
    mutation: RootMutation
});

In my understanding, once I call the nodequery ... on Company, GraphQL will first transverse all types registered with the common interface at interfaces to find out which one to use. It checks if each type is the current one calling isTypeOf and then call the resolve function for the given type.

What I don麓t understand is how will I have an db object instance at the isTypeOf function if the resolve function, where I load data from database, is called after isTypeOf call ?

@leebyron: In other words, if isTypeOf is called before resolve, that will access db, how will isTypeOf be aware of the db information ? Does that makes sense ? BTW: I麓m using mongoose with mongoDB.

@renatonmendes You should import mongoose Company model and in isTypeOf() function check first argument to be instance of this model. All types will return false in type checking except Company type in this case when Interface will traverse every model isTypeOf() function.

isTypeOf() invokes before resolve function of model, which returns true in type checking.

Was this page helpful?
0 / 5 - 0 ratings