Inheritance In Typegraphql GraphQL Typescript

Jan 14th, 2022 - written by Kimserey with .

When working with typegraphql, we expect our models to be translated properly into a graphql schema. Inheritance is one of the feature supported by classes in Typescript. In this post, we will see how we can use inheritance in both objects and resolvers with typegraphql.

Object Inheritance

Object inheritance is directly supported by typegraphql.

1
2
3
4
5
6
7
8
@ObjectType()
export class Person {
  @Field(() => ID)
  id: string;

  @Field(() => String)
  name: string;
}

Here we define a base class Person with id and name,

1
2
3
4
5
@ObjectType()
export class Developer extends Person {
  @Field()
  level: string;
}

and we define a child class extending Person adding level field.

1
2
3
4
5
type Developer {
  id: ID!
  name: String!
  level: String!
}

We can see that the resulting type in the schema is the combination of Person and Developer with the type name Developer. Similarly with ArgsType supports inheritance.

Resolver Inheritance

But typegraphql does not only translates the types, it also translates the resolvers to Query and Mutation.

Typegraphql also has support for inheritance in resolvers through a factory base class.

The factory base class will create a class type applying to the type T provided:

1
2
3
4
5
6
7
8
9
10
11
function createBaseResolver<T extends ClassType>(cls: T) {
  @Resolver({ isAbstract: true })
  abstract class BaseResolver {
    @Query(() => [cls], { name: `getAll${cls.name}` })
    getAll(): T[] {
      return [];
    }
  }

  return BaseResolver;
}

The construct may seem complicated at first, but it relies on a feature of Typescript allowing us to define a class within a function, and return the typeof the class as output of the function, effectively creating a factory for the type.

Every time we would invoke the function, we would receive a new declaration of the type (as opposed to receiving an instance).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function factory() {
  class MessageClass {
    constructor(private msg: string) {}
    say() {
      return this.msg;
    }
  }

  return MessageClass;
}

const MessageClass = factory();
const x = new MessageClass("hello");
x.say();

Then using the declaration, we can new them and receive specific instances.

So going back to the resolver declaration factory, we are able to customise the declaration as we receive a parameter cls of generic type T:

1
2
3
4
5
6
7
@Resolver({ isAbstract: true })
abstract class BaseResolver {
  @Query(() => [cls], { name: `getAll${cls.name}` })
  getAll(): T[] {
    return [];
  }
}

We can then specify the query type to be [cls] and customise the name to be getAll{...}. isAbstract needs to be set to true as we don’t want the resolver to be registered on its own.

T extends ClassType guard is necessary as we want to make sure the type provided is an ObjectType that is accepted by the Query decorator. The ClassType enforces that the type has a constructor - this is all define for us as long as our object types use the ObjectType decorator.

Then with our base resolver type factory, we can create resolvers extending the base resolver:

1
2
3
4
5
@Resolver()
export class PersonResolver extends createBaseResolver(Developer) {}

@Resolver()
export class BookResolver extends createBaseResolver(Book) {}

and we can see that the Query type generated will follow our declarations:

1
2
3
4
type Query {
  getAllDeveloper: [Developer!]!
  getAllBook: [Book!]!
}

And that concludes today’s post on inheritance with typegraphql!

Conclusion

Today we looked at inheritance in typegraphql, we started by looking at how object types could use inheritance, we then moved on to look at how we could implement inheritance in our resolvers. I hope you liked this post and I’ll see you on the next one!

Designed, built and maintained by Kimserey Lam.