The Advantages of Using Graphql Hot Chocolate Library on .Net
In this article, I would like to mention Hot Chocolate Library on .Net platform and explain some key advantages about this library. I have created a sample project named as “CityLibrary.Graphql” and it can be observed on Github. By the way, I had created similar .Net Web Api project for REST version.
In classic graphql, developers can generate some endpoints which enable getting data in a “selection data set” for client applications. However, it doesn’t support any filtering, pagination or ordering in its nature. On the other hand, Hot Chocolate Library provides executing these functionalities efficiently via IQueryable without using any extra libraries. Thanks to IQueryable interface, similar database queries can be executed for different types of databases like MongoDB, Neo4J. Furthermore, it can be easily integrated with EF Core library. The most important point is enabling the execution of these queries on database engines.
I used EF Core with SQLite database in this project, and I will explain some other query execution advantages like data loaders in this article. I don’t mention which packages were installed because it can be seen on Github link easily. Let us start with the configuration on program.cs.
As it can be seen, the graphql server registered with db context (required for Ef Core integration), authorization, query and mutation types, filtering, ordering and projections. These pagination registrations are required for enabling the execution of extra query features. Moreover, fluent validation and global exception error filter were configured there. The last registrations are about data loaders, I will explain the advantages of using data loaders later.
Query and Mutation Type Definitions
In Hot Chocolate library, we can register only one mutation and query types. However, we can register other classes to dependency injection container by using “ExtendObjectType” annotation. Otherwise, partial classes can be used but the first choice is more proper for this purpose.
Authorization
Similar to a rest api, Hot Chocolate provides authorization mechanism with HotChocolate.AspNetCore.Authorization annotation.
Query Executions, Pagination and Data Loaders
I think query execution techniques and pagination are the strongest features of Hot Chocolate Library. It enables executing queries on database side if return type is IQueryable object result as output of query types. In addition, it serves offset and cursor pagination options with sorting and filtering. Let us examine these features with examples in my boilerplate project.
First example is relevant with offset pagination:
As you can see, there are some annotations which enable transforming graphql queries to db queries on “GetAllBooks” method. By the way, Hot Chocolate eliminates some special words like “get” and “async” for graphql queries. Namely, the query can be executed as “allBooks” on client side. I enabled offset queries including total count and restricting maximum page size. These are optional configurations. The default page size configuration is also optional. If query does not include any pagination query, this option determines the default pagination size. UseProjection manages selection set, UseFilter and UseSorting manage filtering and sorting respectively. You can reach more detailed information on Hot Chocolate documentation.
By using “Service” attribute with resolver service kind, we can inject and use specific service in query or mutation types. You can examine the difference service kinds and functionalities by clicking here. Let’s apply a graphql query for “allBooks” and observe the results:
By the way, before execution of this query, we must set bearer token to “Authorization” header after getting jwt from login query:
The result of login query:
We can also investigate sql query and prove that all pagination and filter queries are executed on db engine. I have set up Serilog for monitoring sql queries.
Then, we can see those queries on console and MongoDB “Logs” collection (if you set up the configuration on your machine. You can run MongoDB locally using Docker. See README.md)
First query was executed because include total count option was set to true. These two queries include “DeleteAt” condition because of using EF Core Query Filter. I set for this project, and it can be seen below.
Now, let us take a look at cursor pagination and data loaders. In some cases, resolving fields require different queries. For example, getting the information about member or book in active book reservations requires member and book queries. At this point, two different approaches can be applied. First option can be joining these two tables with main data table (so active book reservations) before returning data to client. Nevertheless, even if the client does not demand the information about member, the member data had been already queried on database via inner join or left join query. On the other hand, if we talk about second option, we can create other resolving query options with the power of graphql. However, we may encounter some other problems if we chose second option such as applying redundant queries for each data row. For instance, if we want to see information about books and members of reservations, we hit the resolving book and member queries for each book reservation. It means querying database for each reservation data. Thus, the second option can be ineffective. Thanks to Hot Chocolate library, we can do query optimisations by using data loaders for these kinds of cases. Let’s dive into code about active book reservation queries:
In MemberLoader and BookLoader, I have inherited from “BatchDataLoader” class which enables gathering resolving field queries. This class requires overriding LoadBatchAsync method as it can be seen. You can follow the link for more information about data loaders. You can observe both book and member services in order to comprehend how these queries optimised.
Then, we must define a resolver type including these two loaders:
There is one crucial point in ActiveBookReservationResolverType.cs file. BookId and MemberId fields must be projected for all cases because they were needed for all specific book and member inner queries. For this reason, it must be added [IsProjected(true)] decorators on those fields. At the last step, this resolver type should be mapped from service.
I used cursor pagination for this query. You can observe example query, result and created database query below.
Summary
To sum up, different database type queries can be integrated with pagination and filters by using IQueryable with Hot Chocolate library. I think this is the best part of Hot Chocolate. We can optimise queries using batch loaders. Of course, we need less endpoints compared with a rest api thanks to graphql but Hot Chocolate library provides some extra features in order to ease developer life. Happy coding :)