Mapping a Relay Connection to an API call.

104 views
Skip to first unread message

Alex Martins

unread,
Mar 17, 2017, 4:38:50 PM3/17/17
to sangria-graphql
Hi everyone, 

I'm totally new to GraphQL, Relay, and Sangria. 
I'm trying to implement a binding between a relay connection pagination and a call to an HTTP API, where the results are paginated by passing page and limit as request parameters. For example "page=1&limit=10" will fetch the first 10 entries and so on.
I was wondering how I would map the Relay connection pagination model to this one. On another thread, someone mentioned using the overloaded version of connectionFromSeq, where you can pass a SliceInfo, but honestly, I couldn't grok that.

Could anyone please help me on that?
Thanks.
Alex.

Oleg Ilyenko

unread,
Mar 17, 2017, 6:07:40 PM3/17/17
to sangria-graphql
Hi Alex,

I would agree, `Connection.connectionFromSeq` can help here. Generally, Relay uses cursor-based pagination, but it's possible to translate it to offset+limit based pagination. I will use `offset` instead of `page` since it's a bit more general concept, but "page" generally represents the same thing (if I understand correctly, in your case page represents a specific number of elements, so `offset = page * limit`).

`connectionFromSeq` is a very simple implementation of connection that uses an element index as a cursor. `index == offset`, so here is how you can use it.

First of all, you need to be able to convert connection arguments (`after`, `before`, `first`, `last`) to a `limit` and `offset`. Here is one example of how you can do it:

case class OffsetLimit(offset: Int, limit: Int)

def getOffset(cursor: Option[String], defaultOffset: Int): Int =
cursor flatMap Connection.cursorToOffset getOrElse defaultOffset

def calculateOffsetLimit(args: ConnectionArgs, total: Int): OffsetLimit = {
val fromOffset = getOffset(args.after, 0)
val toOffset = getOffset(args.before, total - 1)

val startOffset = args.last.fold(fromOffset)(last toOffset - last)
val endOffset = args.first.fold(toOffset)(first math.min(toOffset, startOffset + first))

OffsetLimit(offset = startOffset, limit = endOffset - startOffset)
}

As you probably noticed, you will also need `total` number of entities in the DB/external service. Without it, you would not be able to cover all possible pagination scenarios allowed by relay (it's pretty flexible in this respect).

Now that you have `calculateOffsetLimit` you can use it to calculate offset and limit and load data from external service:

val offsetAndLimit = calculateOffsetLimit(args, total)
val data: Seq[Fruit] = fruitService.loadTastyFruits(offsetAndLimit)

Now `connectionFromSeq` comes into play. `data` contains only a slice of the actual dataset, so you will need to use `SliceInfo` to tell `connectionFromSeq` which part of the data this is:

Connection.connectionFromSeq(data, args, SliceInfo(offsetAndLimit.offset, offsetAndLimit.limit))

This will give you a `Connection[Fruit]` which contains the right page of the data.

I probably made some minor mistakes in the code (just quickly prototyped it), but hope this demonstrated the concept. Let me know if you have further questions.

Cheers,
Oleg
Message has been deleted

Alex Martins

unread,
Mar 20, 2017, 5:36:54 PM3/20/17
to sangria-graphql
Hi Oleg, 

Thanks for your quick response. Page on the API I'm integrating with means the page index. So for example, if I want to fetch the first 10 items I would pass page=0&limit=10, then the next 10, I would pass page=1&limit=10, and so on. The tricky part is to translate the flexibility of fetching based on cursors to this model. Working on that.

Thanks again.
Reply all
Reply to author
Forward
0 new messages