11

Currently, I see no existing support for pagination in the graphql-java library. It does have some basic relay support, where-in, we can create a connection, Facebook's recommended way of implementing pagination.
This is the method which helps achieve that. However, with no documentation I'm finding it hard to understand how this function works. Can someone break-down the steps they would take to add pagination support if they already have an existing model which allows basic queries like Add, delete, fetch etc. using the graphql-java library?

sbrk
  • 1,338
  • 1
  • 17
  • 25

1 Answers1

10

You don't even need Relay connections to support pagination. Your query could simply accept a page number and size (or limit/offset) as arguments and return a list - done. But, if you wanted Relay connection for e.g. Book type, you'd do something like the following:

Relay relay = new Relay();
GraphQLOutputType book = ...; //build your normal Book object type
GraphQLObjectType bookEdge = relay.edgeType(book.getName(), book, null, Collections.emptyList());
GraphQLObjectType bookConnection = relay.connectionType(book.getName(), bookEdge, Collections.emptyList());

As a result, you'd have a BookConnection type that conforms to the Relay connection spec.

As for the example with basic GraphQL, you have a simple web app here.

The connection spec naturally fits a data store that supports cursor based pagination, but needs some creativity when used with different pagination styles.

1) If you wish to use simple offset based paging, you can decide to treat after as the offset (meaning a number would be passed), and first as the limit:

SELECT * FROM ORDER BY timestamp OFFSET $after LIMIT $first

The same for before and last, just different direction.

2) Another way is to treat after/before as the last seen value of the sort column (so an actual (obfuscated) value would be passed):

SELECT * FROM ORDER BY timestamp WHERE timestamp > $after LIMIT $first

I'd also recommend you take a look at my project, graphql-spqr, with an example app, that makes developing GraphQL APIs dead simple.

For example, you'd create a paginated result like this:

public class BookService {
    @GraphQLQuery(name = "books")
    //make sure the argument names and types match the Relay spec
    public Page<Book> getBooks(@GraphQLArgument(name = "first") int first, @GraphQLArgument(name = "after") String after) {
        //if you decide to fetch from a SQL DB, you need the limit and offset instead of a cursor
        //so, you can treat "first" as count as "after" as offset
        int offset = Integer.valueOf(after);
        List<Book> books = getBooksFromDB(first, offset);
        Page<Book> bookPage = PageFactory.createOffsetBasedPage(books, totalBookCount, offset);
        return bookPage;
    }
}

There's many other ways to create a Page instance, this is just the most straight-forward one.

You'd then generate a schema from your Java class:

GraphQLSchema schema = new GraphQLSchemaGenerator()
       .withOperationsFromSingleton(new BookService())
       .generate();
GraphQL graphQL = GraphQLRuntime.newGraphQL(schema).build();

And execute a query:

ExecutionResult result = graphQL.execute("{books(first:10, after:\"20\") {" +
                "   pageInfo {" +
                "       hasNextPage" +
                "   }," +
                "   edges {" +
                "       cursor, node {" +
                "           title" +
                "}}}}");

But, again, if you are not using Relay there's really no need to overcomplicate things. If your storage supports cursor-based pagination naturally, go for it. If it doesn't, just use the simple limit/offset arguments and return a list, and forget the connection spec. It was created to enable Relay to automatically manage paging in various scenarios, so it's almost always a total overkill if you're not using Relay and/or a DB with cursor-based pagination.

kaqqao
  • 12,984
  • 10
  • 64
  • 118
  • Wonderful answer! – sbrk Jun 16 '17 at 14:45
  • @user3728233 Glad to help :) – kaqqao Jun 16 '17 at 14:46
  • For most pagination scenarios, you'll still want to know the total count. How would you handle that by just adding the skip/limit variables? – Donuts Jan 09 '19 at 19:46
  • @Donuts Relay page already has the total count (missing in SPQR at the moment, but will be added soon, also easily added manually). Otherwise, you can return an object type encapsulating the result list and the count. – kaqqao Jan 09 '19 at 21:39
  • @kaqqao I was referring to the option where Relay isn't used. So yeah i see 2 options: 1, your query returns a type that has both the array of results and a total. or 2, in your graphql schema your Query type could contain an additional mapping for a count query -- so you could have books and then booksCount (which returns an int). The drawback for option 1 is that for each type that can be paged, you have to have an additional type to handle the paging because graphql doesn't handle generics. like i can't have PagedResult in my backend map to a PagedResult type in graphql – Donuts Jan 10 '19 at 22:25
  • @Donuts If you're doing it by hand, it is a bit of nuisance, so it might be easier to just stick to the connection spec as the helpers already exist to make generating the connection types easy. If using SPQR, you can certainly set it up to generate the specific GraphQL type from the Java generic on the fly (that's how `Page` works). – kaqqao Jan 10 '19 at 22:41