The following approach can be used to implement an [[AWS AppSync|AppSync]] query or [[AWS API Gateway|API Gateway]] request that needs to query [[DynamoDB]] and return a paged list of items to the client, along with a token allowing the client to request the next page.
## Background
- [[How DynamoDB pagination works]]
## Serialising the `LastEvaluatedKey` in API calls
When implementing API request handlers, you may not wish to expose the value returned from `LastEvaluatedKey` to the client since it's an implementation detail of your data schema—an object with 1 or 2 attributes of varying names whose values your client won't care about. (this is especially true if you're following a [[DynamoDB single-table design|Single-table design]] where pk and sk attribute values will often be composite strings)
The client just needs a token in order to request the next page. Let's call this the `continuationToken`. The simplest way to implement this is to convert the `LastEvaluatedKey` to a JSON string and then base64-encode it and return it as a the `continuationToken` to your API client (whether it's a REST API or GraphQL API).
The token will then be decoded into the `ExclusiveStartKey` on the subsequent request.
## Implementing this in code
The following function shows how this serialisation (and deserialisation) can be performed in a query for all items within a single partition (related to a User entity in this example).
```typescript
const fetchUserItems = async (userPartitionKey: string, limit?: number, continuationToken?: string) => {
let exclusiveStartKey: any;
if (continuationToken) {
try {
exclusiveStartKey = JSON.parse(Buffer.from(continuationToken, 'base64').toString('utf8'));
} catch (error) {
throw new Error('Invalid continuationToken');
}
}
const queryResponse = await documentClient.query({
TableName: MainTable.name,
KeyConditionExpression: 'pk=:pk',
Limit: limit,
ExpressionAttributeValues: {
':pk': userPartitionKey,
},
ExclusiveStartKey: exclusiveStartKey,
}).promise();
return {
items: queryResponse.Items,
continuationToken: queryResponse.LastEvaluatedKey
? Buffer.from(JSON.stringify(queryResponse.LastEvaluatedKey)).toString('base64') : undefined,
};
};
```
This function could be referenced in the Lambda handler for an API Gateway route or AppSync resolver.
## #OpenQuestions
- Is there a potential security vulnerability (akin to SQL injection) caused by returning the `LastEvaluatedKey` to API clients who could then amend it, e.g. change the sort key to access another user's/tenant's data? I'm sure DynamoDB accounts for this somewhere, but I can't see where. Also the data model is unlikely to be designed in such as way that items with differing per-user authorization levels are included within the same partition in the first place.