graphql-inherits icon indicating copy to clipboard operation
graphql-inherits copied to clipboard

@inherits directive for Apollo GraphQL

@inherits Directive for Apollo GraphQL

GraphQL and Apollo Server have no built-in inheritance features.

  • The extend keyword and Apollo @extends directive add fields to existing types and have nothing in common with JavaScript ES6 extends.
  • GraphQL interfaces can't (and are not intended to) share fields or resolvers.

This repo provides a custom directive to achieve inheritance.

@inherits Directive Code

const { ApolloServer, gql, SchemaDirectiveVisitor } = require('apollo-server');

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

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

const resolvers = {
    Query: {
        tesla: () => ({ model: 'S', type: 'electrical' }),
    },
    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,
};

const server = new ApolloServer({
    typeDefs,
    resolvers,
    schemaDirectives,
});

server.listen(9449).then(({ url }) => {
    console.log(`Server ready at ${url}`);
});

Sample Query

Query:

query {
  tesla {
    type
    manufacturer
    papa
    color
    model
  }
}

Output:

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

Limitations

Overriding a resolver requires to declare the field in the derived (child) type.