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 tripCountScannedCountRead unitsLastEvaluatedKey
1271271128.5set
2271271128.5set
3585827.5absent

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: set

Ten 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: lastEvaluatedKey is undefined on the first pass, and that is deliberate: the v3 serializer drops undefined members, so the same object literal works for the first request and every follow-up. Substituting {} — the obvious guess for "start at the beginning" — fails with ValidationException: The provided starting key is invalid.
  • @aws-sdk/client-dynamodb never 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-dynamodb is the wrapper to reach for.
  • Numbers survive the round trip as strings. Unmarshalling {N: '9007199254740993'} with unmarshall from @aws-sdk/util-dynamodb returns a JS bigint, not a lossy number; pass {wrapNumbers: true} and you get {value: '9007199254740993'} instead. Either way, do not Number() a DynamoDB N you did not size-check.
  • KeyConditionExpression takes an equality on the partition key plus at most one sort-key condition (=, <, <=, >, >=, BETWEEN, begins_with). Anything else belongs in a FilterExpression, which runs after the read.
  • ScanIndexForward: false reverses sort-key order; ascending is the default. IndexName switches 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.

References

Build this request visually

Compose this operation in the free DynamoDB Query Builder — key condition, filter, index, Limit, sort order, and a pagination loop — and copy it back as a runnable SDK v3, CLI, or boto3 program.

Open the DynamoDB Query Builder

Work with DynamoDB without the Console

A fast DynamoDB desktop client that runs the real SQL DynamoDB can’t — JOINs, GROUP BY, aggregates — with visual editing and an AI agent on your own Bedrock keys.

Free 30-day trial, no credit card — then the Free plan with no time limit.