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.