Querying Redis with GraphQL using Redis OM .NET
Tags: redis, dotnet, graphql, tutorial
Published: 2023-12-14 01:55:20


> Note: This post is part of C# Advent 2023, to check out the entire series check out the whole [Calender](https://csadvent.christmas/) Big shoutout to [@mgroves](https://twitter.com/mgroves) for organizing.

Earlier this year I received an [issue](https://github.com/redis/redis-om-dotnet/issues/409) whereby one of our users had taken to extending [Redis OM .NET](https://github.com/redis/redis-om-dotnet) to integrate with GraphQL and was experiencing difficulty. Redis OM .NET is a LINQ-based Redis [Search and Query](https://redis.io/docs/interact/search-and-query/) client.

Now, integrating with GraphQL is not a supported feature, but given that the user had contributed to the library in the past, and offered up their own PR, I figured I ought to take a look. Now, I'm not by any stretch of the imagination a GrpahQL expert, but by golly the thing seemed to actually work. Not only that, but it seemed to work with minimal intervention needed from the user. So in this post I'm going to lay out what you need to do to get GraphQL issuing well-formed Redis Queries.

> Note: this is not offically supported by Redis OM .NET
> Big shoutout to [Rohan Edman](https://github.com/zulander1) whose issue inspired this post.

## Prerequisties

* .NET 7 SDK
* Docker
* A GraphQL IDE (I'm using [Banana Cake Pop](https://chillicream.com/docs/bananacakepop/v2))

## Start Redis Stack

First and foremost we need to start our Redis Stack instance, just run `docker run -p 6379:6379 redis/redis-stack-server` or otherwise your favorite way to run Redis Stack.

## Jump straight to the code

All the code for this project can be found by cloning this Repo.

```sh
git clone https://github.com/slorello89/redis-om-graphql-example
```

You can just clone that and 

## Integrating HotChocolate with Redis OM .NET

Hot Chocolate is part of the Chilli Cream GraphQL platform that serves as a GraphQL Server for .NET instances. That's what we'll be using to esentially serve as the first step of middleware in our translation layer between GraphQL and Redis.

### Create the project

Create the project by running:

```sh
dotnet new web -n GraphQL
```

And changing directories into the project.

```
cd GraphQL
```

### Add Redis.OM and Hot Chocloate Packages

Now you just need to add the hot chocloate and Redis OM Packages:

```
dotnet add package Redis.OM
dotnet add package HotChocolate.AspNetCore --version 13.7.0
dotnet add package HotChocolate.Data --version 13.7.0
```

### Fetch the data needed for this project

In the source Repo, there's a folder called [`data`](https://github.com/slorello89/redis-om-graphql-example/tree/main/data), download these json files and add them to a new directory in the root of your project called `data`. Then in the .csproj file add the following directive to see that they are copied to the build directory:

```xml
<ItemGroup>
    <Content Include="./data/*.*">
        <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
</ItemGroup>
```

### Create Model

We are going to be inserting a bunch of book data into Redis, we need to add a simple data model to do this:


```cs
using Redis.OM.Modeling;

namespace GraphQL;

[Document(StorageType = StorageType.Json)]
public class Book
{
    [Indexed]
    [RedisIdField]
    public string? Id { get; set; }
    
    [Searchable]
    public string? Title { get; set; }

    [Searchable]
    public string? SubTitle { get; set; }

    [Searchable]
    public string? Description { get; set; }

    [Indexed]
    public string? Language { get; set; }

    [Indexed]
    public long? PageCount { get; set; }

    [Indexed]
    public string? Thumbnail { get; set; }

    [Indexed]
    public double? Price { get; set; }

    [Indexed]
    public string? Currency { get; set; }

    public string? InfoLink { get; set; }

    [Indexed]
    public string[]? Authors { get; set; }
}
```

In Redis Terms, the `Indexed` attributes indicate that they will be stored as tag fields for Exact matches, and `Searchable` indicates that the field will be stored as a Text field for full-text search.

### Create Bootstrapping Service

We will add an `IHostedService` to seed Redis with all the necessary data. Create a file called `StartupService.cs` and add the following:

```cs
using System.Text.Json;
using Redis.OM;
using Redis.OM.Contracts;

namespace GraphQL;

public class StartupService : IHostedService
{
    private readonly IRedisConnectionProvider _provider;

    public StartupService(IRedisConnectionProvider provider)
    {
        _provider = provider;
    }

    public async Task StartAsync(CancellationToken cancellationToken)
    {
        var books = _provider.RedisCollection<Book>();
        _provider.Connection.DropIndexAndAssociatedRecords(typeof(Book));
        await _provider.Connection.CreateIndexAsync(typeof(Book));

        var files = Directory.GetFiles("data");
        foreach (var file in files)
        {
            var tasks = new List<Task>();
            var str = await File.ReadAllTextAsync(file, cancellationToken);
            var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
            var booksInFile = JsonSerializer.Deserialize<Book[]>(str, options);
            foreach (var book in booksInFile)
            {
                tasks.Add(books.InsertAsync(book));
            }

            await Task.WhenAll(tasks);
        }

    }

    public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
```

At startup this will:

1. Drop the Books index and anything that was in it.
2. Create the Books index
3. Insert all the books read out of our json files in the `data` directory into Redis

### Create Query Class

Hot Chocolate requires you to have a query class to perform the queries on your data. Let's just crate a file `Query.cs` and add the following to it:

```cs
using HotChocolate.Resolvers;
using Redis.OM.Contracts;
using Redis.OM.Searching;

namespace GraphQL;

[ExtendObjectType("Query")]
public class Query
{
    private readonly IRedisCollection<Book> _books;

    public Query(IRedisConnectionProvider provider)
    {
        _books = provider.RedisCollection<Book>();
    }

    [UsePaging(IncludeTotalCount = true, MaxPageSize = 10)]
    [UseProjection]
    [UseFiltering]
    public IQueryable<Book> GetBook(IResolverContext context) => _books.Filter(context);
}
```

In here, we are depenency injecting an `IRedisConnectionProvider` to create the Books collection, and then we are simply calling the extension `Filter` on our Books collection using the ResolverContext, that comes from HotChocolate tellin the IQueryable `_books` how to query for books in Redis. the `IRedisCollection<Book>` is an `IQueryable` that just knows how to properly parse the expressions it's provided by Hot Chocolate into a usable RediSearch Query.

### Configure our Services / App

Finally, in Program.cs we need to depenency inject our IRedisConnectionProvider:

```cs
builder.Services.AddSingleton<IRedisConnectionProvider>(new RedisConnectionProvider("redis://localhost:6379"));
```

then we need to add our StartupService:

```cs
builder.Services.AddHostedService<StartupService>();
```

then we need to configure our GrpahQL Service:

```cs
builder.Services.AddGraphQLServer()
    .AddProjections()
    .AddFiltering()
    .AddQueryType(d => d.Name("Query"))
    .AddType<Query>();
```

Notice how we are adding projection, filtering, and our query type.

Then we need to build our app and map our GraphQL service:

```cs
var app = builder.Build();

app.MapGet("/", () => "Hello World!");
app.MapGraphQL();
app.Run();
```

And that's it!

## Start the Project

To start the project just run `dotnet run` from the directory you clone the GitHub repo into.

The exact endpoint that it will take on will displayed in your terminal, in my case the endpoint is `http://localhost:5097`

Open up your GraphQL GUI of choice, and point it at `<Your-endpoint>/graphql`, et voila, you have a graphQL endpoint that will faithfully take your GraphQL queries and attempt to parse them as Redis Queries. take the following example:

![Banana Cake Pop GUI](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cl5sfnr4cfmkhxfgvfkb.png)

This query:

``` graphql
query {
  book(where: { description: { eq: "Redis" } pageCount: {lt: 100}}) {
    nodes {
      title
      subTitle
      description
      pageCount, 
      infoLink
    }
  }
}
```

Is translated to:

```sh
"FT.SEARCH" "book-idx" "((@PageCount:[-inf (100]) (@Description:\"Redis\"))" "LIMIT" "0" "11" "RETURN" "15" "Title" "AS" "Title" "SubTitle" "AS" "SubTitle" "Description" "AS" "Description" "PageCount" "AS" "PageCount" "$.InfoLink" "AS" "InfoLink"
```

In Redis, which is exactly what you would expect. and it does all of this with what minimal code and effort.

## Resources

* The code for this post is all in [its Repo](https://github.com/slorello89/redis-om-graphql-example)
* You can Learn more about Hot Chocolate on their [website](https://chillicream.com/)
* To learn more about Redis OM, visit [its Repo](https://github.com/redis/redis-om-dotnet)
* To learn more about Redis, visit [redis.io](https://redis.io/)