Implementing Cursor-Based Pagination with APPSYNC_JS and DynamoDB
Table of contents
TL;DR
This article offers insights on implementing cursor-based pagination with DynamoDB and APPSYNC_JS by setting up a serverless application, creating a basic schema, building resolvers, and deploying the service using Serverless Framework and AppSync Plugin. Test the pagination by seeding data and querying records with nextToken.
Introduction
With great Serverless power, comes great integration responsibilities
- Cloud Hashira ๐ฅ
In this article, we'll explore the powerful capabilities of Amazon DynamoDB and its seamless integration with a wide range of applications and use cases. While its flexibility is impressive, certain compromises are necessary as some operations that are simple with databases like DocumentDB or RDS require additional steps and methods.
We'll guide you through implementing Cursor-Based pagination with DynamoDB and APPSYNC_JS runtime, providing valuable insights along the way.
APPSYNC_JS? That a thing?
On Nov. 17, 2022, AWS published the release of Javascript runtime, enabling developers using APPSYNC to easily test, evaluate, and debug their code in their local environment, and also provide more flexibility to define data access business logic while creating their resolvers. New to APPSYNC_JS? You can learn more here.
Pagination with DynamoDB
By default, AWS DynamoDB supports cursor-based pagination. This means that when you run a query in DynamoDB with the potential to return a large number of records, by passing a limit
in your request without passing a value for ExclusiveStartKey
or nextToken
(in the case of AppSync) it fetches the records that match the condition and keys in your query from the first match to that within the specified limit. Along with the records, a LastEvaluatedKey
or a nextToken
is returned if there are still records that match your query but are outside the specified limit.
This key or token simply acts as a pointer or a cursor on where the next query operation is going to start fetching records. For flexibility, scalability, and efficiency, DynamoDB implements this type of pagination perfectly with some trade-offs. Due to the way it is implemented, you cannot:
Skip to a specific page
View the total number of pages
Generally, sort features are limited
With that being said, let's build our solution.
Building the Solution
Basic Setup
We will be building our solution using Serverless Framework, Nodejs, and of course, an AWS account. I will assume that you have aws cli
installed and your credentials configured. If not, check out how to install and set up aws cli to be on the same note.
Folder Set Up
From your terminal, run the following commands:
mkdir appsync_pagination_ddb
cd appsync_pagination_ddb
npm init -y
mkdir src
mkdir schema
touch serverless.yml
touch .eslintrc.json
Installing Required Packages
npm i --save-dev serverless-appsync-plugin
npm i --save-dev @aws-appsync/eslint-plugin @aws-appsync/utils
code .
In your .eslintrc.json
paste the code below. The appsync eslint-plugin serves as a syntax validator for APPSYNC_JS runtime.
{
"extends": ["plugin:@aws-appsync/base"]
}
Defining Our Schema
We will create a basic Book
type for our schema. This will include a single Query
resolver for listing records. Follow the steps below to create the necessary files and their corresponding contents.
schema/types.graphql
type Book {
id: ID!
title: String!
author: String!
copies: Int
createdAt: AWSDateTime
updatedAt: AWSDateTime
}
type ListBooksResult {
books: [Book!]!
nextToken: String
}
type Query {
listBooks(input: ListBooksInput): ListBooksResult!
}
schema/inputs.graphql
input ListBooksInput {
nextToken: String
limit: Int
}
Creating the Resolvers
As defined already in schema/types.graphql
, we will be creating one query resolver to list and paginate items from the database. The resolver would simply map out inputs from the appsync API to DynamoDB params in the request
function and the corresponding result in the response
function. Follow the steps below to create the necessary files and their corresponding contents.
src/resolvers/listBooks.js
import { util } from "@aws-appsync/utils";
export function request(ctx) {
return {
operation: "Scan",
limit: ctx.args.input.limit ?? 10,
nextToken: ctx.args.input.nextToken
}
}
export function response(ctx) {
if (ctx.error) return util.error("Books Not Found");
return {
books: ctx.result.items,
nextToken: ctx.result.nextToken
};
}
As previously discussed, cursor-based pagination is implemented by default in DynamoDB. In the listBooks.js
resolver, the request
function accepts the limit
and nextToken
parameters. nextToken
is always null
for the initial request or if you want to start fetching from the beginning of the records. the response
function returns the records within the specified limit that matches the query
. If there are still records that match the query, a signed value is returned for nextToken
which the DB will use to return the next set of records.
IaC with Serverless Framework and AppSync Plugin
NB: The serverless-appsync-plugin version that supports APPSYNC_JS runtime is currently v2xx. It has slightly different configuration rules in comparison with v1xx. Check out the differences and guidelines on how to upgrade from v1xx to v2xx here.
Align your serverless.yml
with the code below for infrastructure setup.
service: appsync-pagination-dynamodb
frameworkVersion: "3"
configValidationMode: error
plugins:
- serverless-appsync-plugin
provider:
name: aws
region: us-west-2
appSync:
name: pagination-dynamodb-api
schema:
- schema/*.graphql
logging:
level: ERROR
authentication:
type: API_KEY
apiKeys:
- name: myApiKey
dataSources:
library:
type: AMAZON_DYNAMODB
config:
tableName: !Ref Library
resolvers:
Query.listBooks:
functions:
- dataSource: library
code: src/resolvers/listBooks.js
resources:
Resources:
Library:
Type: AWS::DynamoDB::Table
Properties:
TableName: library-${sls:stage}
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
Deploy Service
Run the command below to deploy the service to your AWS account.
sls deploy -s dev --verbose
After successfully deploying the service, turn on your GraphBolt and take the resolvers for a spin. GraphBolt is your perfect AppSync companion. It holds all your APIs in one place and you can easily select, run, inspect, and debug your API operations.
You can use the AWS AppSync Console to test your API as well.
Testing
Seeding Data
Let us add some records to the DynamoDb we just created. Run the command below to fetch the data we are going to be writing to our database.
curl https://raw.githubusercontent.com/NwekeChidi/appsync_pagination/main/booksData.json --output booksData.json
Run the command below to create records in the DynamoDB table using the data in booksData.json
file.
aws dynamodb batch-write-item --request-items file://booksData.json --output json
Testing the Resolver
Launch your GraphBolt and select the profile where the solution is deployed to if you have multiple profile credentials configured on your local, then select the corresponding API we just deployed, click the auth
icon, select API Key
and click save
.
You can run the query listBooks
when you are through creating the records to see the cursor-based pagination in play.
Passing the value of nextToken
in the input for your next query returns the next set of records within the specified limit
.
Conclusion
Implementing Cursor-Based Pagination with DynamoDB and APPSYNC_JS is a powerful and flexible technique for managing large datasets in serverless applications.
By following the steps outlined in this article, you can effectively set up your serverless application, create a basic schema, build resolvers, and deploy the service using Serverless Framework and AppSync Plugin. Keep in mind the trade-offs and limitations of this approach, and consider extending the practice with more advanced operations DynamoDB indexes for further optimization and faster data access.
To mitigate some of the cons presented by this operation, the client side of your application can persist the nextToken
as page numbers and increment the total number of records
per new fetch with the last returned token until the value for nextToken
is null. This will enable users of your solution to skip between previously accessed pages.
You can find the complete code from this article on my Github.
Don't forget to clean up afterward. sls remove
to the rescue!
Happy Coding and Sayonara!๐
Subscribe to my newsletter
Read articles from Chidimma Gabriel Dominic Nweke directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Chidimma Gabriel Dominic Nweke
Chidimma Gabriel Dominic Nweke
๐ Backend serverless developer skilled in machine learning ๐ค and building scalable solutions with AWS โ๏ธ. Passionate about driving innovation and creating intelligent applications ๐ป๐. Let's connect and make an impact in the world of backend development! ๐ป๐ #Serverless #MachineLearning #AWS #Backend #Innovation