dynamodb-paginator

A library to help paginate dynamodb results into pages

Usage no npm install needed!

<script type="module">
  import dynamodbPaginator from 'https://cdn.skypack.dev/dynamodb-paginator';
</script>

README

npm NPM David CircleCI codecov

NOTE: This pagination library only works on indexes with a range key.

Usage

Compatible with AWS SDK v2 and v3

import { DynamoDB } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocument } from '@aws-sdk/lib-dynamodb';
import { getPaginatedResult, decodeCursor } from 'dynamodb-paginator';

interface User {
    id: string
    name: string
}

const documentClient = DynamoDBDocument.from(new DynamoDB({}));

const limit = 25;
const standardQueryParams = {
    TableName: 'Users',
    Limit: limit,
    KeyConditionExpression: 'id = :id',
    ExpressionAttributeValues: {
        ':id': '1'
    }
};
// Could be a cursor from a previous paginated result
const cursor = undefined;
const paginationParams = decodeCursor(cursor) || standardQueryParams;

const result = await documentClient.query(paginationParams);

// By default the cursors are encoded in base64, but you can supply your own encoding function
const paginatedResult = getPaginatedResult<User>(paginationParams, limit, result);
// Output:
// {
//     data: T[],
//     meta: {
//         limit: number,
//         hasMoreData: boolean,
//         cursor: string,
//         backCursor: string,
//         count: number
//     }
// }

Security disclaimer

It's important to validate that the cursor has been generated by your service before passing it to the DynamoDB. If you don't, this opens a NoSQL vulnerability. A solution for this is signing/encrypting the cursor with a key.

Without encrypting the cursor, the partition and range key are also visible to the client consuming the cursor.

If your service offers authentication, it's also wise to validate that the cursor being parsed, was originally generated for that user/session. This is to prevent replay attacks.

Cursor encryption example

A simplified example of encrypting and decrypting the generated pagination cursor.

It's recommended to encapsulate the secured pagination code in a service, for ease of use.

import { randomBytes, createCipheriv, createDecipheriv } from 'crypto';
import { getPaginatedResult, decodeCursor } from 'dynamodb-paginator';

const ENC_KEY = randomBytes(32); // set random encryption key
const IV = randomBytes(16); // set random initialisation vector
const ALGORITHM = 'aes-256-cbc';

const encrypt = ((val) => {
    const cipher = createCipheriv(ALGORITHM, ENC_KEY, IV);
    let encrypted = cipher.update(JSON.stringify(val), 'utf8', 'base64');
    encrypted += cipher.final('base64');

    return encrypted;
});

const decrypt = ((encrypted) => {
    const decipher = createDecipheriv(ALGORITHM, ENC_KEY, IV);
    const decrypted = decipher.update(encrypted, 'base64', 'utf8');

    return JSON.parse((decrypted + decipher.final('utf8')));
});

const params = { TableName: 'Users' };
const limit = 25;
// Example DynamoDB Output
const result = {
    Items:
        [
            { id: 1, email: 'a@example.com' },
            { id: 2, email: 'b@example.com' },
        ],
    Count: 2,
    LastEvaluatedKey: { id: 2 },
};

// Pass a custom encoding function
const paginatedResult = getPaginatedResult(params, limit, result, encrypt);

// Pass a custom decoding function
const decodedCursor = decodeCursor(paginatedResult.meta.cursor, decrypt);

console.log(decodedCursor);
// Output:
// {
//     TableName: 'Users',
//     ExclusiveStartKey: {id:2},
//     previousKeys: [{id:2}],
//     back: false
// }

API Reference

Functions

getPaginatedResult(params, limit, result, cursorEncodingFunction)PaginatedResult<T>
decodeCursor(encodedCursor, cursorDecodingFunction)DynamoDBParams | undefined

Typedefs

DynamoDBParams : Object
DynamoDBResult : Object
MetaData : Object
PaginatedResult : Object

getPaginatedResult(params, limit, result) ⇒ PaginatedResult<T>

Kind: function

Param Type
params DynamoDBParams
limit number
result DynamoDBResult
cursorEncodingFunction? (cursor: DynamoDBParams) => string

decodeCursor(cursor) ⇒ Cursor | undefined

Kind: function

Param Type
encodedCursor string
cursorDecodingFunction? (encodedCursor: string) => DynamoDBParams

DynamoDBParams : Object

Kind: object Properties

Name Type Description
TableName string The name of the table containing the requested items
[IndexName] string The name of a secondary index to scan
[AttributesToGet] any This is a legacy parameter. Use ProjectionExpression instead.
[Limit] number The maximum number of items to evaluate
[Select] any The attributes to be returned in the result
[ScanFilter] any This is a legacy parameter
[ConditionalOperator] any This is a legacy parameter
[ExclusiveStartKey] any The primary key of the first item that this operation will evaluate
[ReturnConsumedCapacity] any Adds the consumed capacity to the result
[TotalSegments] any For a parallel Scan request
[Segment] any For a parallel Scan request
[ProjectionExpression] string A string that identifies one or more attributes to retrieve from the specified table or index
[FilterExpression] string A string that contains conditions that DynamoDB applies after the Scan operation
[ExpressionAttributeNames] any One or more substitution tokens for attribute names in an expression
[ExpressionAttributeValues] any One or more values that can be substituted in an expression
[ConsistentRead] boolean A Boolean value that determines the read consistency model during the scan

DynamoDBResult : Object

Kind: object Properties

Name Type Description
[Items] any An array of item attributes that match the scan criteria
[Count] number The number of items in the response
[ScannedCount] number The number of items evaluated
[LastEvaluatedKey] any The primary key of the item where the operation stopped
[ConsumedCapacity] any The capacity units consumed by the Scan operation

MetaData : Object

Kind: object Properties

Name Type Description
limit number The limit of the amount of returned items
hasMoreData boolean True if not all items in the DynamoDB table were returned that match the query
cursor string Used for pagination if there are more items left
backCursor? string Used for paginating back to previous results
count number The amount of items returned

PaginatedResult : Object

Kind: object Properties

Name Type Description
data T The queried data
meta MetaData Metadata regarding the result