Search code examples
typescriptamazon-dynamodbaws-appsync

AWS AppSync DynamoDB JS resolver - forward and backwards pagination


so I am trying to implement a GQL API using AppSync where I can query a paginated array of items that are stored on a dynamoDB table. My goal is to implemente a forward and backwards pagination system directly on AppSyncJS resolvers

I have the following schema:

type ConnectionPageInfo {
    endCursor: String
    hasNextPage: Boolean!
    hasPreviousPage: Boolean!
    startCursor: String
}

type Item {
    id: ID!
    name: String!
}

type ItemEdge {
    node: Item!
}

type ItemPaginatedResponse {
    edges: [ItemEdge!]!
    pageInfo: ConnectionPageInfo!
}

type Query {
    listItems(
        first: Int,
        last: Int,
        after: String,
        before: String
    ): ItemPaginatedResponse
}

This way when calling listItems query I can send:

  • Both first + after params so I can go forward
  • Both last and before params so I can navigate backwards.

The main issue is that I was expecting the token to be some sort of base64 encoding representation of the pk of the dynamoDB table but it is not. Which makes me not able to generate the startCursor token. I also tried to obtain the startCursor or previousToken token directly from the dynamoDB response but I did not find how

My current AppSyncJS resolver looks like this:

import { Context, util } from '@aws-appsync/utils';

export function request(ctx: Context) {
  const organisationId = 'organisation-1'; // this will be a parameter
  const { first, last, after, before } = ctx.arguments;

  const limit = first ?? last ?? 10;
  const nextToken = after ?? before ?? undefined;
  const scanIndexForward = !!first;

  return {
    version: '2017-02-28',
    operation: 'Query',
    query: {
      expression: '#key = :value',
      expressionNames: { '#key': 'organisationId' },
      expressionValues: { ':value': util.dynamodb.toDynamoDB(organisationId) },
    },
    limit,
    nextToken: nextToken ? nextToken : undefined,
    scanIndexForward,
  };
}

export function response(ctx: Context) {
  if (ctx.result?.error) {
    return util.error(ctx.result.error.message, ctx.result.error.type);
  }
  console.log('obtained result:', ctx.result);

  const { items } = ctx.result;
  return {
    pageInfo: {
      endCursor: ctx.result.nextToken,
      hasNextPage: !!ctx.result.nextToken,
      hasPreviousPage: false, // NOT RELEVANT NOW
      startCursor: '', // I DON'T KNOW HOW TO GENERATE THIS BASED ON FIRST ITEM ON THE RESULT ARRAY
    },
    edges: items.map((item: { id: string; name: string }) => {
      return { node: { id: item.id, name: item.name } };
    }),
  };
}

Is there any way to:

  1. Generate an AppSync dynamoDB token based on a dynamoDB object?
  2. Make AppSync dynamoDB resolver to return the previousToken on the ctx.result object?

I tried to decode the token returned on nextToken but was not able to.


Solution

  • For anyone who checks this topic. After further investigation and after reaching AWS support it is confirmed that it is not possible to paginate forward + backwards strictly on AppSyncJS dynamoDB resolvers. It is not posible neither to generate nextToken(s) manually for specific items.

    There is two possible workarounds for this:

    • Implement on a lambda instead of AppSyncJS dynamoDB resolvers, this way we can make usage of LastEvaluatedKey and ExclusiveStartKey
    • Keep pagination logic somewhere else (FE or another service) Basically keep PageNumber - Token relation on a Map/Array so we can resolve this logic somewhere else.