Generic Type In Typegraphql GraphQL Typescript

Jan 21st, 2022 - written by Kimserey with .

Last week, we looked at how typegraphql was supporting inheritance, translating it into a proper graphql schema. Today we will look at another common feature of Typescript, generic types and how they can be translated properly into a graphql schema.

Basic Support

The basic support for generic type can be achieved through inheritance.

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

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

For example if we wanted to use the Person class and have a ServiceResponse type which would look as followed:

1
2
3
4
export class ServiceResponse<T> {
  data: T;
  statusCode: string;
}

We want the data field to be typed as the type T so we want typegraphql to translate properly ServiceResponse<Person>. In order to do that we can use the same technique as we used for inheritance described in last week post, where we uses a class declaration factory:

1
2
3
4
5
6
7
8
9
10
11
12
export function ServiceResponse<T>(cls: ClassType<T>) {
  @ObjectType({ isAbstract: true })
  abstract class ServiceResponse {
    @Field(() => cls)
    data: T;

    @Field()
    statusCode: string;
  }

  return ServiceResponse;
}

We define an inner abstract class, our ServiceResponse, and return the declaration.

1
2
@ObjectType()
export class PersonServiceResponse extends ServiceResponse(Person) {}

We can then create a new object type extending the result of the factory we created. And just use it in our resolver:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Resolver()
export class PersonResolver {
  @Query(() => PersonServiceResponse)
  getPerson(): PersonServiceResponse {
    return {
      data: {
        id: "123",
        name: "kim",
      },
      statusCode: "success",
    };
  }
}

This will generate the following schema:

1
2
3
4
5
6
7
8
9
10
11
12
13
type Query {
getPerson: PersonServiceResponse!
}

type PersonServiceResponse {
data: Person!
statusCode: String!
}

type Person {
id: ID!
name: String!
}

We can see that we have a Person type and a PersonServiceResponse type as we wanted.

Type Factory

We can also omit creating the extra object type by making the ServiceResponse class not abstract, and instead naming manually the object:

1
2
3
4
5
6
7
8
9
10
11
12
export function ServiceResponse<T>(cls: ClassType<T>) {
  @ObjectType(`${cls.name}ServiceResponse`)
  class ServiceResponse {
    @Field(() => cls)
    data: T;

    @Field()
    statusCode: string;
  }

  return ServiceResponse;
}

We then must create a instance of the declaration and extract the type of the declaration so that we can use it in our resolver:

1
2
export const PersonServiceResponse = ServiceResponse(Person);
export type PersonServiceResponse = InstanceType<typeof PersonServiceResponse>;

and using the same resolver, we would achieve the same schema:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Resolver()
export class PersonResolver {
  @Query(() => PersonServiceResponse)
  getPerson(): PersonServiceResponse {
    return {
      data: {
        id: "123",
        name: "kim",
      },
      statusCode: "success",
    };
  }
}

Note that Typescript allows us to name the instance the same name as the type, which allows us to export both with a single export.

And that concludes today’s post!

Conclusion

In today’s post we looked at how typegrapql supports generic and translate into graphql schema. We started by looking at the basic setup through inheritance and then looked at how we could avoid the inheritance. I hope you liked this post and I’ll see you on the next one!

Designed, built and maintained by Kimserey Lam.