Some more advanced GraphQL concepts with HotChocolate - Part 5

In this article, we will observe how HotChocolate manages filtering.

How to use filetring in GraphQL ?

In a previous post, we observed how a GraphQL query could be parameterized to specifically retrieve the desired customer.

1type Query {
2  customer(id: "123f0") {
3    id
4    age
5  }
6}

This parameterized query exemplifies filtering, wherein we specify our intention to retain only a subset of data based on a specific criterion, such as the customer ID in our scenario. However, it's evident that we might also seek to retrieve data based on other criteria, necessitating the authoring of additional GraphQL queries, as demonstrated below.

1type Query {
2  customers(name: "Peter Smith") {
3    id
4    age
5  }
6}

This query will require the implementation of a dedicated resolver, as depicted below.

1public List<Customer> GetCustomersByName(string name)
2{
3    // In a real-world scenario, we would fetch data from a datastore using the appropriate query.
4    return _customers.Where(t => t.Name == name).ToList();
5}

However, an issue arises here, as we would only fetch customers with exact matching names, whereas the actual objective would likely involve retrieving customers whose names contain the specified pattern.

It is however possible to be more explicit in a GraphQL query by introducing a more complex model.

 1type Query {
 2  customers(where: CustomerFilterInput){
 3    id
 4    age
 5  }
 6}
 7
 8input StringOperationFilterInput {
 9  and: [StringOperationFilterInput!]
10  or: [StringOperationFilterInput!]
11  eq: String
12  neq: String
13  contains: String
14  ncontains: String
15  in: [String]
16  nin: [String]
17  startsWith: String
18  nstartsWith: String
19  endsWith: String
20  nendsWith: String
21}
22
23input CustomerFilterInput {
24  and: [CustomerFilterInput!]
25  or: [CustomerFilterInput!]
26  name: StringOperationFilterInput
27}

With this capability at hand, it becomes feasible to construct rather intricate queries, given that the requisite resolver has been implemented.

 1query {
 2  customers(  
 3    where: {
 4      or: [{ name: { contains: "Smith" } }, { name: { startsWith: "Jeffrey" } }]
 5    }
 6  ) {
 7    id
 8    age
 9  }
10}

At the cost of a more intricate model, we can now fetch customers whose names contain "Smith" OR start with "Jeffrey".

Everything appears to be in order, but now it's time to implement all of this in C#! Fortunately, HotChocolate simplifies the process for us by handling the heavy lifting.

How is SDL integrated with HotChocolate ?

HotChocolate by default will inspect your .NET model and infer the possible filter operations from it.
https://chillicream.com/docs/hotchocolate/v13/fetching-data/filtering

That's great news: HotChocolate will automatically observe our .NET classes and generate the SDL we just discussed. However, to enable this functionality, we need to configure our application a bit.

Configuring the C# code

  • Add the HotChocolate.Data package using the NuGet Package Manager within Visual Studio.

  • Edit the StartUp class with the following code.
 1public class StartUp : FunctionsStartup
 2{
 3    public override void Configure(IFunctionsHostBuilder builder)
 4    {
 5        ConfigureServices(builder.Services);
 6    }
 7
 8    private static void ConfigureServices(IServiceCollection services)
 9    {
10        services.AddSingleton<ICustomerRepository, MockCustomerRepository>();
11
12        services.AddGraphQLFunction()
13            .AddQueryType<Query>()
14            .AddMutationType<Mutation>()
15            .AddFiltering();
16    }
17}
  • Edit the Query class with the following code.
 1public class Query
 2{
 3    [UseFiltering]
 4    public IQueryable<Customer> GetCustomers([Service] ICustomerRepository customerRepository)
 5    {
 6        return customerRepository.GetAllCustomers().AsQueryable();
 7    }
 8
 9    // ...
10}

That's it ! With just a few lines of code, we can create an effective implementation. The responsibility of the resolver would then be to intelligently retrieve only the data we require, but that's a story for another time.

Consuming the service

We will now transition to the client project.

  • Add an indexFiltering.html file and fill it with the following code.
 1<html>
 2<head>
 3    <title>GraphQL</title>
 4</head>
 5<body>
 6    <pre><code class="language-json" id="code"></code></pre>
 7    <script src="https://cdnjs.cloudflare.com/ajax/libs/js-beautify/1.14.9/beautify.min.js"></script>
 8    <script>
 9        (async function () {            
10            const data = JSON.stringify({
11                query: `query {
12                          customers(
13                            where: {
14                              name: { contains: "Smith" } 
15                            }) {
16                               id
17                               name
18                               age
19                            }
20                       }`
21            });
22
23            const response = await fetch(
24                'http://localhost:7132/api/graphql',
25                {
26                    method: 'post',
27                    body: data,
28                    headers: {
29                        'Content-Type': 'application/json'
30                    },
31                }
32            );
33
34            const json = await response.json();
35            document.getElementById('code').innerHTML = js_beautify(
36                JSON.stringify(json.data)
37            );
38        })();
39    </script>
40</body>
41</html>
  • Run the program

Therefore, we can observe that we receive only the specified customers. Once again, with just a single endpoint, HotChocolate handles the heavy lifting for us. This makes setting up API endpoints quick and straightforward.

Information

For your information, Azure API Management (APIM) offers the capability to define GraphQL endpoints alongside traditional REST endpoints. This illustrates the growing popularity and diffusion of GraphQL as a technology choice for building APIs.

Final thoughts

That concludes this series, and we will explore even more advanced features in future series. We will particularly explore how GraphQL can be deployed on Azure using Azure API Management (APIM).

The subsequent textbooks prove useful for concluding this series.

Learning GraphQL: Declarative Data Fetching for Modern Web Apps (Porcello, Banks)

Apps and Services with .NET 8: Build practical projects with Blazor, .NET MAUI, gRPC, GraphQL, and other enterprise technologies (Price)