DynamoDB Query in Node.js (AWS SDK v3)
A complete Query in AWS SDK v3 is the do/while below, not the single client.send() most snippets show: one page is capped at 1 MB, and the rest of the partition only arrives if you feed LastEvaluatedKey back. See Query vs. Scan for when Query is the right read at all.
Code
import {DynamoDBClient, QueryCommand} from '@aws-sdk/client-dynamodb';
const client = new DynamoDBClient({region: 'us-east-1'});
const items = [];
let lastEvaluatedKey;
do {
const response = await client.send(
new QueryCommand({
TableName: 'Music',
KeyConditionExpression: '#hashKey = :hashKeyValue AND begins_with(#rangeKey, :rangeKeyValue)',
ExpressionAttributeNames: {
'#hashKey': 'Artist',
'#rangeKey': 'SongTitle'
},
ExpressionAttributeValues: {
':hashKeyValue': {S: 'Arturo Sandoval'},
':rangeKeyValue': {S: 'C'}
},
ExclusiveStartKey: lastEvaluatedKey
})
);
items.push(...(response.Items ?? []));
lastEvaluatedKey = response.LastEvaluatedKey;
} while (lastEvaluatedKey);
console.log(`Found ${items.length} items`);What the loop actually does
Against a 600-song fixture, every song ~3.9 KB and all under Artist = "Arturo Sandoval", the loop above sends three requests:
| Round trip | Count | ScannedCount | Read units | LastEvaluatedKey |
|---|---|---|---|---|
| 1 | 271 | 271 | 128.5 | set |
| 2 | 271 | 271 | 128.5 | set |
| 3 | 58 | 58 | 27.5 | absent |
Nobody configured 271. That is where 1 MB ran out, so the page boundary moves whenever your item size does. A partition that pages once today pages twice after you add an attribute, and code that reads response.Items from a single send() silently returns 271 of 600 songs with no error.
Now add Limit: 10 and a FilterExpression on Year to the same query:
Count: 0 ScannedCount: 10 ConsumedCapacity: 5 LastEvaluatedKey: setTen items evaluated, zero returned, and the request still cost read capacity. Limit bounds what DynamoDB reads, and the filter runs after that, so a Limit chosen to mean "give me 10 results" gives you between 0 and 10.
Measured 2026-07-28 against DynamoDB Local (amazon/dynamodb-local) with @aws-sdk/client-dynamodb 3.1095.0 on node v24.18.0. Counts and capacity are the engine's own response fields.
Explanation
ExclusiveStartKey: lastEvaluatedKeyisundefinedon the first pass, and that is deliberate: the v3 serialiser dropsundefinedmembers, so the same object literal works for the first request and every follow-up. Substituting{}— the obvious guess for "start at the beginning" — fails withValidationException: The provided starting key is invalid.@aws-sdk/client-dynamodbnever marshals for you. Values go in as{S: 'Arturo Sandoval'}and items come back the same way. That is the trade for not pulling in the DocumentClient; if you would rather write plain JS objects,@aws-sdk/lib-dynamodbis the wrapper to reach for.- Numbers survive the round trip as strings. Unmarshalling
{N: '9007199254740993'}withunmarshallfrom@aws-sdk/util-dynamodbreturns a JSbigint, not a lossynumber; pass{wrapNumbers: true}and you get{value: '9007199254740993'}instead. Either way, do notNumber()a DynamoDBNyou did not size-check. KeyConditionExpressiontakes an equality on the partition key plus at most one sort-key condition (=,<,<=,>,>=,BETWEEN,begins_with). Anything else belongs in aFilterExpression, which runs after the read.ScanIndexForward: falsereverses sort-key order; ascending is the default.IndexNameswitches the same command to a secondary index.
Do it visually
The DynamoDB query builder emits this whole shape — key condition, name and value maps, and the LastEvaluatedKey loop — as a runnable SDK v3 program, so the pagination is not the part you forget.
To run queries against real tables in a GUI, with a key-condition form and a paginated results grid, download DynoTable.
Related guides
- Query vs. Scan — why
Queryis the right default. - Key condition expressions — every legal partition/sort-key operator.
- "Query condition missed key schema element" — the key condition names the wrong attribute or skips the partition key.
- "Query key condition not supported" — an operator the key condition can't use, like contains or a second sort-key condition.