GraphQL pagination with DynamoDB - Cursor specification
Tags: graphql, aws, webdev, node
Published: 2021-04-28 10:13:43

> Note: This is a multi-part series; if you already know about relay pagination, feel free to [skip to the next section](https://dev.to/andyrichardsonn/graphql-pagination-with-dynamodb-dynamodb-pagination-425m).

Relay's [Cursor Connection Specification](https://relay.dev/graphql/connections.htm) has become the industry standard for pagination in GraphQL.

Whether you use a Relay, Apollo, or urql, **the spec is supported by many client side libraries**. This makes it an easy choice for those who don't want to implement their own client-side pagination logic (which is most of us).

# 💬 Pagination grammar

Before we declare a pageable field, you're going to want to know about the following primitives.

### Edge

An [_Edge_](https://relay.dev/graphql/connections.htm#sec-Edges) is a container type which ensures that each item in a paginated response (_node_) is accompanied with a position in the collection (_cursor_).

```graphql
type UserEdge {
  node: User!
  cursor: String!
}
```

### PageInfo

[_PageInfo_](https://relay.dev/graphql/connections.htm#sec-undefined.PageInfo) is a metadata type which provides information about a pagination request.

```graphql
type PageInfo {
  startCursor: String
  endCursor: String
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
}
```

### Connection

A [_Connection_](https://relay.dev/graphql/connections.htm#sec-Connection-Types) is a container type which aggregates _Edge_ and _PageInfo_ values and is the root type of any paginated response.

```graphql
type UserConnection {
  edges: [UserEdge!]!
  pageInfo: PageInfo!
}
```

# 🎨 Creating a pageable field

Adding pagination support to a field is pretty straightforward. The field needs to take the following four arguments - `first`, `after`, `last`, and `before` (more on this later) - and return a _Connection_.

```graphql
type Query {
  users(
    first: Int,
    after: String,
    last: Int, 
    before: String
  ): UserConnection!
}
```

# 🏎️ Page traversal

Paginated fields take four arguments which can be divided into two distinct groups.

## Forward pagination

[Forward pagination](https://relay.dev/graphql/connections.htm#sec-Forward-pagination-arguments) is the process of fetching pages in a collection from top-to-bottom.

### Arguments

The arguments to use forward pagination are:
 - `first` - the number of items to return (required)
 - `after` - a cursor resembling the position in the collection to start from (optional)

![Forward pagination](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9c3wq5zlnby1yejgyya8.gif)

### Retrieval

As you can see above, the first paginated query will include only the `first` argument. This tells the backend to start at the top of the collection.

For subsequent queries the `after` argument is used to instruct the backend where to continue from. This value corresponds to the _cursor_ of the last _edge_ on the previous response.

## Backward pagination
[Backward pagination](https://relay.dev/graphql/connections.htm#sec-Backward-pagination-arguments) is the process of fetching pages in a collection from bottom-to-top.

### Arguments
The arguments to use backward pagination are:
 - `last` - the number of items to return (required)
 - `before` - a cursor resembling the position in the collection to start from (optional)

![Backward pagination](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gd2t4d50xj4lgzbmhqrt.gif)

### Retrieval

This second example might look familiar - that's because it's almost identical to forward pagination. 

Just like with forward pagination, the page size is sent in isolation for the first query (this time `last` rather than `first`).

Again, for subsequent queries, a cursor is provided to instruct the backend where to continue from (this time `before` rather than `after`). This value corresponds to the _cursor_ on the first (as opposed to last) _edge_ on the previous response. 

It's important to note, the sort order in which edges are returned is the same regardless of whether forward or backward pagination is used.

---

Thanks for reading! 

If you enjoyed this post, be sure to react 🦄  or drop a comment below with any thoughts 🤔. 

You can also hit me up on twitter - [@andyrichardsonn](https://twitter.com/andyrichardsonn)

Disclaimer: All thoughts and opinions expressed in this article are my own.