GraphQL

Write an awesome doc for GraphQL. A very nice an practical one extracted from GraphQL official documentation.

View on GitHub

Pagination

[!NOTE]

  • Different pagination models enable different client capabilities.
  • I tried prisma-nestjs-graphql lib and here is the result: apps/botprobe-nest project.
    • I decided to try out nestjs-query since it looks more promising both from filtering capabilities, pagination and auth (learn more here).
  • To lean more about pagination in general read this.
  • To learn more about efficiency in SQL you can read this.
  • Read this great article in apollo website Relay-Style Connections and Pagination.
  • A related post to this topic from a bigger point of view would be: Zero, One, Infinity Principle in Software Development.

To traverse the relationship between sets of objects. we can have a field that returns a plural type:

{
  post {
    id
    title
    comments {
      id
      content
      user {
        id
        username
      }
    }
  }
}

Cursor-based pagination in GraphQL

[!TIP]

Since the “cursor” is opaque, it can be anything. E.g. in nestjs-query by default it is not using a keyset cursor approach. JFYI, it does not matter what is our ORM/ODM, what matters is to understand that due to the opaque nature of cursor nestjs-query by default uses a type offset pagination beneath the cursor-based pagination.

learn more about nestjs-query’s pagination here and now let’s look at the GraphQL query and its generated SQL query by the TypeOrm:

Note that we have both previous page and next page in this particular example.
SQL Query GraphQL Query
```sql SELECT "Alert"."id" AS "Alert_id", "Alert"."title" AS "Alert_title", "Alert"."userId" AS "Alert_userId", "Alert"."updatedAt" AS "Alert_updatedAt" "Alert"."createdAt" AS "Alert_createdAt", "Alert"."alertTypeId" AS "Alert_alertTypeId", "Alert"."description" AS "Alert_description", FROM "alert" "Alert" LIMIT 5 OFFSET 13 ``` ```graphql query { alerts(paging: { "paging": { "last": 4, "before": "YXJyYXljb25uZWN0aW9uOjE4" } }) { edges { cursor node { id title createdAt } } pageInfo { endCursor hasNextPage startCursor hasPreviousPage } } } ```
```sql SELECT "Alert"."id" AS "Alert_id", "Alert"."title" AS "Alert_title", "Alert"."description" AS "Alert_description", "Alert"."userId" AS "Alert_userId", "Alert"."alertTypeId" AS "Alert_alertTypeId", "Alert"."createdAt" AS "Alert_createdAt", "Alert"."updatedAt" AS "Alert_updatedAt" FROM "alert" "Alert" LIMIT 7 ``` ```graphql query { alerts(paging: { "paging": { "first": 6 } }) { edges { cursor node { id title createdAt } } pageInfo { endCursor hasNextPage startCursor hasPreviousPage } } } ```
```sql SELECT "Alert"."id" AS "Alert_id", "Alert"."title" AS "Alert_title", "Alert"."description" AS "Alert_description", "Alert"."userId" AS "Alert_userId", "Alert"."alertTypeId" AS "Alert_alertTypeId", "Alert"."createdAt" AS "Alert_createdAt", "Alert"."updatedAt" AS "Alert_updatedAt" FROM "alert" "Alert" LIMIT 7 OFFSET 6 ``` ```graphql query { alerts(paging: { "paging": { "first": 6, "after": "YXJyYXljb25uZWN0aW9uOjU=" } }) { edges { cursor node { id title createdAt } } pageInfo { endCursor hasNextPage startCursor hasPreviousPage } } } ```

How can we send the cursor to the client?

Relay cursor connections

[!TIP]

Lovely specification:

To ensure a consistent implementation of this pattern, we can take a loo at the Relay Cursor Connections Specification.

Add a new layer of indirection

Every item in the paginated list has its own cursor

[!IMPORTANT]

# Buzzwords:

  • Connection is the paginated field on an object, e.g. comments in post.
  • Each edge has:
    • Metadata About one object in the paginated list.
    • A cursor to allow pagination starting from that object.
  • Node: represents the actual object user was looking for.
  • pageInfo:
    • Lets the client know if there are more pages of data to fetch.
    • In Relay spec it does NOT tell you the total number of items, because the client cache doesn’t need that info.
      • But we can add it too through another field outside of pageInfo.

To help you better understand the naming convention you can think of it as graph:

Nodes and edges in a graph

Why Were Connections Created?

Designing GraphQL Schema

Cursor-based connections pagination

[!CAUTION]

Sort edges the same way, in both, forward pagination and backward pagination. I.e.,

  • after: the edge closest to cursor must come first.
  • before: the edge closest to cursor must come last.

[!TIP]

In graph theory, an edge/connection can have properties of its own which act effectively as metadata;

enum CommentSentiment {
  NEUTRAL
  POSITIVE
  NEGATIVE
}
type PostCommentEdge {
  cursor: ID!
  node: Comment
  sentiment: CommentSentiment
}

Note: most tools treat the edge type as boilerplate. But we are not gonna do that, we add data that belongs to the edge to the edge type.

3rd-party libs

Ref

  1. Pagination.
  2. Understanding pagination: REST, GraphQL, and Relay.
  3. Explaining GraphQL Connections.
  4. Implementation of Relay-style pagination with Prisma Client JS.