How to implement isTypeOf method?
Asked Answered
V

1

17

Given this schema:

interface INode {
  id: ID
}

type Todo implements INode {
  id: ID
  title: String!
}

type Query {
  node(id: ID!): INode
}

Given this class:

export default class Todo {
  constructor (public id: string, public title: string) { }

  isTypeOf(value: any): Boolean {
    return value instanceof Todo;
  }
}

Given this resolver:

type NodeArgs = {
  id: string
}
export const resolver = {
  node: ({ id }: NodeArgs) => {
    return new Todo('1', 'Todo 1');
  }
}

When I call the query:

query {
  node(id: "1") {
    id
    ... on Todo {
      title
    }
  }
}

Then I get the return below:

{
  "errors": [
    {
      "message": "Abstract type INode must resolve to an Object type at runtime for field Query.node with value { id: \"1\", title: \"Todo 1\" }, received \"undefined\". Either the INode type should provide a \"resolveType\" function or each possible type should provide an \"isTypeOf\" function.",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "node"
      ]
    }
  ],
  "data": {
    "node": null
  }
}

As you can see, I've implemented the isTypeOf function but I am still getting the error message.

What am I doing wrong?

Notes:

  • I am using Typescript, express and express-graphql;
Vice answered 31/10, 2018 at 7:46 Comment(0)
I
30

isTypeOf is a function that is passed to the constructor of a GraphQLObjectType when you create your schema programatically. Ditto for resolveType functions and unions/interfaces. If you use SDL and create your schema using buildSchema, there is no way to inject those functions into your created schema, just like you don't have a way to provide resolvers for fields on types other than Query and Mutation.

You have a couple of options. One option is to utilize the default resolveType behavior. This checks for a __typename property on the object, and falls back to calling isTypeOf on every implementing type until it matches. That means if you're using a class, it should be sufficient to do something like this:

export default class Todo {
  get __typename() {
    return 'Todo'
  }
}

The better option would be to drop buildSchema and use makeExecutableSchema from graphql-tools. Then you can define your resolveType and/or isTypeOf functions directly in your resolvers. For example:

const resolvers = {
  Query: {
    node: (obj, args, context, info) => {
      return new Todo('1', 'Todo 1')
    }
  },
  INode: {
    __resolveType: (obj, context, info) => {
      if (obj instanceof Todo) return 'Todo'
    },
  }
}

Not only can you easily define isTypeOf or resolveType this way, you can also easily add resolvers for fields of any type and add custom scalars without any hassle. You cannot do any of that (easily) if you're using just buildSchema.

Edit:

If you prefer to utilize isTypeOf instead of resolveType, the resolvers would look something like this:

const resolvers = {
  Query: {
    node: (obj, args, context, info) => {
      return new Todo('1', 'Todo 1')
    }
  },
  Todo: {
    __isTypeOf: (obj, context, info) => {
      return obj instanceof Todo
    },
  }
}

Only one or the other is necessary. Either write a resolveType function for every abstract type you use, or write a isTypeOf for every object type that could be an abstract type.

Ieshaieso answered 31/10, 2018 at 11:44 Comment(8)
Daniel, Thank you for your time. I've tried the first suggestion and it doesn't work, so now I'm considering proceed with your second suggestion: use makeExecutableSchema. However I have one concern. I'm trying to create a Relay Server and makeExecutableSchema is from Apollo. So far I know, Relay is a "pattern" and Apollo is a "framework" with different implementation. So my question is: Wouldn't be incorrect use both in the same solution?Vice
If you're looking to integrate graphql-relay-js to make a relay-compliant server, I believe that forces you generate a schema programatically using vanilla GraphQL.js -- building a schema from SDL using either buildSchema or makeExecutableSchema probably won't let you incorporate all the various helper methods included with the relay library. Otherwise, it's possible to have a relay-compliant server built with graphql-tools or even apollo-server.Ieshaieso
I'm curious what about the first approach "doesn't work"? What errors or behavior were you seeing? Here's a working example using Launchpad that demonstrates what I was suggesting: launchpad.graphql.com/07wkrx5qj5 (Launchpad uses makeExecutableSchema, but the concept is still the same)Ieshaieso
"If you're looking to integrate graphql-relay-js to make a relay-compliant server, I believe that forces you generate a schema programatically using vanilla GraphQL.js" - That's exactly what I am trying to do! =)Vice
Regarding the error, it's my fault: I've changed just the implementation of todo class and I kept it using buildSchema from 'graphql.js' (not makeExecutableSchema from 'graphql-tools'). I'll keep trying understand how to implement the relay server with vanilla GraphQL!Vice
Can the answer be updated to include an isTypeOf implementation? That was the original question but it doesn't seem to actually explain that way.Idioblast
I wonder in the future if I won't be able to type my keyboard because the type of my fingers wont' work with the expected type of my keys. You want absurd. welcome to GQL and TS. Were you endlessly code code for the sake of coding code and everything has a type but no one stops to ask why. Good luck.Cultism
remember when our lives were easy and we didnt have to do anything of this extra typing crap.Cultism

© 2022 - 2024 — McMap. All rights reserved.