Can a GraphQL input type inherit from another type or interface?

Graphql

Graphql Problem Overview


Is it possible to use inheritance with GraphQL input types?

Something like that (this, of course, doesn't work with input types):

interface UserInputInterface {
  firstName: String
  lastName: String
}

input UserInput implements UserInputInterface {
  password: String!
}

input UserChangesInput implements UserInputInterface {
  id: ID!
  password: String
}

Graphql Solutions


Solution 1 - Graphql

No, the spec does not allow input types to implement interfaces. And GraphQL type system in general does not define any form of inheritance (the extends keyword adds fields to an existing type, and isn't for inheritance). The spec is intentionally constrained to stay simple. This means that you're stuck repeating fields across input types.

That said, depending on the way you construct your schema, you could build some kind of type transformer that appends the common fields programmatically based on some meta-data, e.g. a directive.

Better yet, you might be able to solve your problem via composition (always keep composition over inheritance in mind). E.g.

input Name {
  firstName: String
  lastName: String
}

input UserInput {
  name: Name
  password: String!
}

input UserChangesInput {
  name: Name
  id: ID!
  password: String
}

The client now has to send an object a level deeper, but that doesn't sound like much of a price for avoiding big repeating chunks. It might actually be good for the client as well, as they can now have common logic for building names, regardless of the query/mutation using them.

In this example, where it's only 2 simple fields, this approach is an overkill, but in general - I'd say it's the way to go.

Solution 2 - Graphql

Starting with the June2018 stable version of the GraphQL spec, an Input Object type can extend another Input Object type:

> Input object type extensions are used to represent an input object type which has been extended from some original input object type.

This isn't inheritance per se; you can only extend the base type, not create new types based on it:

extend input MyInput {
  NewField: String
}

Note there is no name for the new type; the existing MyInput type is extended.

The JavaScript reference implementation has implemented Input Object extensions in GraphQL.js v14 (June 2018), though it's unclear how to actually pass the extended input fields to a query without getting an error.

For actual type inheritance, see the graphql-s2s library.

Solution 3 - Graphql

It's doable using a custom directive.

Code Summary

const typeDefs = gql`
  directive @inherits(type: String!) on OBJECT

  type Car {
    manufacturer: String
    color: String
  }
  
  type Tesla @inherits(type: "Car") {
    manufacturer: String
    papa: String
    model: String
  }
  
  type Query {
    tesla: Tesla
  }
`;

const resolvers = {
    Query: {
        tesla: () => ({ model: 'S' }),
    },
    Car: {
        manufacturer: () => 'Ford',
        color: () => 'Orange',
    },
    Tesla: {
        manufacturer: () => 'Tesla, Inc',
        papa: () => 'Elon',
    },
};

class InheritsDirective extends SchemaDirectiveVisitor {
    visitObject(type) {
        const fields = type.getFields();
        const baseType = this.schema.getTypeMap()[this.args.type];
        Object.entries(baseType.getFields()).forEach(([name, field]) => {
            if (fields[name] === undefined) {
                fields[name] = { ...field };
            }
        });
    }
}

const schemaDirectives = {
    inherits: InheritsDirective,
};

Query:

query {
  tesla {
    manufacturer
    papa
    color
    model
  }
}

Output:

{
  "data": {
    "tesla": {
      "manufacturer": "Tesla, Inc",
      "papa": "Elon",
      "color": "Orange",
      "model": "S",
    }
  }
}

Working example at https://github.com/jeanbmar/graphql-inherits.

Solution 4 - Graphql

If you came here looking for an explanation for the "implements", keyword, here it is:

> An object type must be a super‐set of all interfaces it implements. The object type must include a field of the same name for every field defined in an interface.

(Excerpt taken from the June 2018 GraphQL spec.)

Here's an example


interface Foo {
  id: ID!
  foo: Int!
}

type Bar implements Foo @entity {
  id: ID!;
  foo: Int!;
  bar: Int!;
}

So the Bar type doesn't inherit from the Foo interface, but it implements it. The former must include all the fields that are listed in the latter.

I think that this is a nice way to annotate types that should be like other types.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionkyrisuView Question on Stackoverflow
Solution 1 - GraphqlkaqqaoView Answer on Stackoverflow
Solution 2 - GraphqlDan DascalescuView Answer on Stackoverflow
Solution 3 - GraphqlJean-Baptiste MartinView Answer on Stackoverflow
Solution 4 - GraphqlPaul Razvan BergView Answer on Stackoverflow