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 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.
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!
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!