Skip to main content

TypeGraphQL Resolver

In this section, we will go over the structure of a GraphQL custom resolver in portal-next and how to create a custom resolver in portal-next alongside with those automatically generated by TypeGraphQL-Prisma.

Prerequisite

It is recommended that you have an understanding of how dependency injection is used in portal-next, which is specified here.

Structure of GraphQL custom resolver in portal-next

In portal-next repo, resolvers are grouped into classes based on the object type that they work with and each class of resolver will have a corresponding service layer.

Consider the following example:

@Resolver(() => SignedURL)
export default class SignedURLResolver {
constructor(private signedURLService: SignedURLService) {}

@Mutation(() => SignedURL)
async transferFile() {
const url = await this.signedURLService.generateV4SignedUrl(options, session?.id);
return {
action: options.action,
fileType: options.fileType,
url,
};
}
}

In this example, it can be seen that we have a SignedURLResolver, which groups all resolvers that will work with a SignedURL object, which is specified by the @Resolver(() => SignedURL) decorator.

Another thing to notice is that in portal-next's architecture, a resolver will not directly communicate with backend infrastructures, but will do so through a service layer. This is demonstrated in the following line:

const url = await this.signedURLService.generateV4SignedUrl(options, session?.id);

In this example, the logic to generate a v4 signed url is delegated to the SignedURLService. This is to ensure the separation of resolver logic from business logic, which is in the service layer.

How to create a custom GraphQL resolver class in portal-next

In portal-next, since resolvers are grouped by the resource that they will be working with, it is necessary that we need to establish the resource object type that the class will be working with.

In the above example, all resolvers revolves around the SignedURL object, which has the following definition:

@ObjectType()
export default class SignedURL {
@Field(() => Action, { nullable: true })
public action!: Action;

@Field(() => FileCategory, { nullable: true })
public fileType!: FileCategory;

@Field({ nullable: true })
public url!: string;
}

To better understand the above definition, check out TypeGraphQL documentation.

To specify that a class is a resolver class that will be working with the SignedURL object, we add the @Resolver(() => SignedURL) on top of the class header. Refer to the following code for more details:

@injectable()
@singleton()
@Resolver(() => SignedURL)
export default class SignedURLResolver {
note

For more details regarding @injectable() and @singleton(), refer to this section about dependency injection of the documentation.

As the architecture of portal-next revolves around separation of business logic from resolver logic, we will need to specify that the resolver class will have a service as its dependency, which can be done as follow in the constructor:

@Resolver(() => SignedURL)
export default class SignedURLResolver {
constructor(private signedURLService: SignedURLService) {}
}
note

Refer to the dependency injection section of this documenntation for more details regarding how the SignedURLService is instantiated and injected into the resolver class.

Following the constructor, we can now create all the query and mutation resolvers that are necessary to work with the resource of interest. The following is an example of a mutation resolver:

@Mutation(() => SignedURL)
async transferFile() {
const url = await this.signedURLService.generateV4SignedUrl(options, session?.id);
return {
action: options.action,
fileType: options.fileType,
url,
};
}
info

It is recommended that all business logic should be delegated to the service layer instead of being implemented directly within the resolver function.

Ideally, the role of the resolver function should only be to communicate with the service layer and return appropriate data.

Additional Materials

To learn more about the library that is used to build portal-next's GraphQL infrastructure, check out this documentation.