Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement offset pagination with Connection in Hot Chocolate?

Tags:

hotchocolate

As doc says Note: Connections are often associated with cursor-based pagination, due to the use of a cursor. Nonetheless, since the specification describes the cursor as opaque, it can be used to facilitate an offset as well.

Note: While we support offset-based pagination, we highly encourage the use of Connections instead. Connections provide an abstraction which makes it easier to switch to another pagination mechanism later on.

but no example provided.

Any idea how to implement offset pagination with Connection in Hot Chocolate? Its not clear for me.

like image 421
van9petryk Avatar asked Sep 06 '25 03:09

van9petryk


2 Answers

It's pretty straightforward!

The cursor can be anything you want, so just return the index + 1 as the endCursor.

Here's an example that uses a static list of integers as the data.

private List<int> _data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
[UsePaging(MaxPageSize = 10, AllowBackwardPagination = false)]
public Connection<int> GetMyThing(string? after, int first = 5)
{
    var skip = int.Parse(after ?? "0");
    var edges = new List<Edge<int>>();
    var index = 0;
    foreach (var item in _data.Skip(skip).Take(first))
    {
        edges.Add(new Edge<int>(item, (skip + index + 1).ToString()));
        index++;
    }
    var pageInfo = new ConnectionPageInfo(
        _data.Count > skip + first, // has next page
        skip > 0, // has previous page
        edges.First().Cursor, // startCursor
        edges.Last().Cursor); // endCursor
    return new Connection<int>(edges, pageInfo);
}

https://chillicream.com/docs/hotchocolate/v13/fetching-data/pagination/#custom-pagination-logic

like image 82
iCodeSometime Avatar answered Sep 07 '25 20:09

iCodeSometime


I've found myself asking exactly the same question when reading the Hot Chocolate docs. Although I haven't found a completely satisfying answer for myself, I'll share my insights on this. Hope it helps you to implement offset paging with connections.

You could use the [UseOffsetPaging] middleware to implement offest paging, but as the docs say this is discouraged, so it's not the solution we're looking for.

The GraphQL Pagination best practices describe three different approaches how to implement paginagion: offset-based friends(first:2 offset:2), id-based friends(first:2 after:$friendId) and cursor-based friends(first:2 after:$friendCursor). It recommends using the cursor-based approach with opaque cursors because this can be used to implement both offset-based and id-based paginagion by making the cursor either the offset or the ID. So the key question is, how's the cursor defined.

I've found, that the Hot Chocolate middleware uses the offset as cursor per default. Imagine a database containing some products data and a query resolver exposing the product data using the paging middleware. I'm using the Entity Framework Core integration to fetch the product items from the database.

public class Product
{
    [ID]
    public int Id { get; set; }
    public string? Name { get; set; }
}
public class Query
{
    [UsePaging(IncludeTotalCount = true, AllowBackwardPagination = false)]
    [UseSorting]
    public IQueryable<Product> GetProducts(ApplicationDbContext dbContext)
    {
        return dbContext.Products;
    }
}

This creates the following schema reference: products connection schema reference

When querying the products with the following query:

query Products{
  products(first: 3, order: {id: ASC}){
    edges{
      cursor
      node{
        id
        name
      }
    }
    totalCount
  }
}

it returns

{
  "data": {
    "products": {
      "edges": [
        {
          "cursor": "MA==", #base64 decoded: "0"
          "node": {
            "id": "3",
            "name": "Product A"
          }
        },
        {
          "cursor": "MQ==", #base64 decoded: "1"
          "node": {
            "id": "5",
            "name": "Product B"
          }
        },
        {
          "cursor": "Mg==", #base64 decoded: "2"
          "node": {
            "id": "8",
            "name": "Product C"
          }
        }
      ],
      "totalCount": 6
    }
  }
}

So Hot Chocolate is actually using an offset-based paging. If you want to query n items from index i you can do this by querying products(first: $n, after: $cursor) where $cursor is a base64 encoded string containing the result of i-1.

This way you can implement an offset-based paging using GraphQL connections.

Where I haven't found a satisfying answer ist how to actually use id-based pagination with Hot Chocolate. It would then use the product Id as cursor instead of the offset. I imagine there should be a way to configure Hot Chocolate to use the ID as cursor, but unfortunately I haven't found a way to do that yet...

like image 29
larahina Avatar answered Sep 07 '25 20:09

larahina