Writing DynamoDB code by hand? Dynobase turns supported data operations into application code or scripts, and generates CDK, CloudFormation or Terraform for an existing table. Code generation in Dynobase
Use AWS SDK for JavaScript v3 with DynamoDBDocumentClient from @aws-sdk/lib-dynamodb to read and write native JavaScript objects. Use DynamoDBClient from @aws-sdk/client-dynamodb for table management and low-level DynamoDB JSON. SDK v3 sends command objects with client.send(); the v2 .promise() pattern does not apply.
Use an ES module, such as a .mjs file, on Node.js 22 or 24. Configure the AWS Region and credentials through an AWS profile or runtime role. The item examples use an Orders table with String partition key pk and String sort key sk; each JavaScript example is standalone. Create the table below first, or use your own matching table. Run destructive examples only against disposable data.
Reuse clients across application requests. Do not put access keys in source code. For IAM Identity Center profiles, sign in with aws sso login --profile your-profile before running local examples.
The low-level client manages tables; the document client marshals native objects for item operations. Set removeUndefinedValues explicitly if optional fields should be omitted. This does not turn undefined into a request to remove a stored attribute; use an update expression for that.
Set AWS_REGION and, for local development, AWS_PROFILE when running the later examples with new DynamoDBClient({}). A Lambda execution role supplies credentials without a profile. Install and bundle the SDK packages with your application.
Create a table
Define only key attributes in AttributeDefinitions, not every property an item can contain. This on-demand table uses pk and sk as String keys and includes the CustomerIndex used later.
Run this after the other examples if you created a disposable table for practice.
List tables
Table listings are paginated and scoped to the configured account and Region. Iterate every page instead of assuming the first response contains every table.
import{ DynamoDBClient, paginateListTables }from"@aws-sdk/client-dynamodb";const client =newDynamoDBClient({});forawait(const page ofpaginateListTables({ client },{})){for(const name of page.TableNames ??[]) console.log(name);}
Scan every page
Scan reads one page at a time. Continue with LastEvaluatedKey until it is absent, processing each page as it arrives instead of loading the whole table into memory.
Scans consume read capacity and do not provide a snapshot. Prefer Query for application lookups or export to S3 for large analytical jobs.
Get one item
Use GetCommand with the full primary key to read one item. The document client returns native JavaScript values; Item is undefined when no matching item exists.
A TypeScript type assertion does not validate data from DynamoDB at runtime. Validate the returned object before treating it as your domain model.
Batch get with retries
Use BatchGetCommand with known full primary keys. Results can be unordered or partial; the loop below retries only unprocessed keys with a bounded backoff.
Deduplicate keys and split requests at 100 keys. Missing items are omitted. If retries are exhausted, retain pending keys in your application and surface the partial failure.
Create an item without overwriting
PutCommand normally replaces an existing item. Add attribute_not_exists(pk) to reject a write when that full primary key already exists; the check and write happen atomically.
Handle ConditionalCheckFailedException as an existing-item conflict. For a composite primary key, the condition applies to the item identified by both key attributes.
Batch write with retries
BatchWriteCommand accepts complete items to put and complete keys to delete. This example writes two key-only sample items and retries unprocessed operations; each put replaces any existing item with that key.
Chunk larger inputs into at most 25 operations per request and respect the payload limit. Batch writes do not support partial updates, conditions or all-or-nothing execution.
Query a partition
KeyConditionExpression selects one partition-key value and can optionally narrow its sort key. Names are aliased with # and values are bound with :; pagination retrieves all matching items.
Non-key attributes belong in FilterExpression. A filter cannot replace the required partition-key equality and does not reduce the capacity used by items already read.
Query a secondary index
Use QueryCommand with IndexName and the GSI partition-key attribute. This example assumes CustomerIndex uses customerId as its partition key.
GSIs support eventually consistent reads only. Results include only attributes projected into the index; a GetCommand can retrieve the base-table item by its full key.
Write a transaction
Use TransactWriteCommand when all writes must succeed or fail together. This creates an order and an audit item with different keys, so it respects the one-action-per-item rule.
Use a unique token for each business operation and reuse the same token and payload when retrying that operation. The token's idempotency window is 10 minutes. The literal here is for one sample order, not every order in an application.
Transactions support up to 100 distinct items and 4 MB of aggregate item data in one account and Region. They cannot span Regions or run against MRSC global tables. A failed condition cancels the whole transaction. See the transaction API reference.
Read a transaction
TransactGetCommand reads known base-table keys atomically. Results correspond to request order; a missing item has no Item field. Use this when separate Get requests could observe an inconsistent combination of related records.
import{ DynamoDBClient }from"@aws-sdk/client-dynamodb";import{ DynamoDBDocumentClient, TransactGetCommand }from"@aws-sdk/lib-dynamodb";const ddb = DynamoDBDocumentClient.from(newDynamoDBClient({}),{marshallOptions:{removeUndefinedValues:true},});const result =await ddb.send(newTransactGetCommand({TransactItems:[{Get:{TableName:"Orders",Key:{pk:"CUSTOMER#123",sk:"ORDER#003"}}},{Get:{TableName:"Orders",Key:{pk:"AUDIT#ORDER#003",sk:"CREATED"}}},],}));for(const response of result.Responses ??[]) console.log(response.Item);
This is not a transactional Query. Supply every full key, stay within 100 items and 4 MB, and use base tables rather than indexes. The read transaction reference describes cancellation and response ordering.
Query with sorting and read the latest items
ScanIndexForward: true sorts ascending; false sorts descending by the sort key. Number keys use numeric order and String keys use UTF-8 byte order. DynamoDB has no table-wide insertion order. For items in one partition, store a sortable creation timestamp as sk and query in descending order. This retrieves the latest timestamp values, not the most recently modified items.
Use uniform UTC timestamps, with a tie-breaker when two items may share a timestamp. For latest items across customers, design an appropriate index; avoid a single hot global key at high write rates.
Query and scan with paginators
The document client's paginateQuery and paginateScan manage LastEvaluatedKey for you. Import these from @aws-sdk/lib-dynamodb when using a document client. The low-level package's paginators expect the low-level client and DynamoDB attribute values.
Each request evaluates at most 1 MB before filtering. pageSize sets the request's Limit, which counts evaluated items, not matches. A filtered page may be empty while more pages remain. Process pages as they arrive; collecting a large table into one array can exhaust memory.
Update selected attributes
An update changes only the named attributes. The condition here prevents accidentally creating a new item:
Compare the current status inside the same request that changes it. This prevents two workers from independently transitioning an order from PENDING after one already shipped it.
A missing item or different status raises ConditionalCheckFailedException. Re-read and decide what the business operation should do instead of blindly repeating the write.
Increment a counter
Use ADD on a Number attribute to increment it atomically and return the updated value. A missing counter starts at zero; the request can create the item if the key is absent.
An increment is not idempotent. Retrying after an ambiguous timeout can increment twice. Use an idempotency record or conditional transaction for exactly-once business effects.
Delete one item
Supply both key attributes. Without a condition, deleting a missing item succeeds; ALL_OLD returns the item that was removed if one existed.
import{ DynamoDBClient }from"@aws-sdk/client-dynamodb";import{ DynamoDBDocumentClient, DeleteCommand }from"@aws-sdk/lib-dynamodb";const ddb = DynamoDBDocumentClient.from(newDynamoDBClient({}),{marshallOptions:{removeUndefinedValues:true},});const result =await ddb.send(newDeleteCommand({TableName:"Orders",Key:{pk:"CUSTOMER#123",sk:"ORDER#001"},ReturnValues:"ALL_OLD",}));
console.log(result.Attributes ??"Item was already absent");
Add a ConditionExpression when deletion depends on an expected status or version.
Batch delete known keys
BatchWriteCommand can delete a set of known full primary keys. Each delete is atomic, but the batch is not; retry UnprocessedItems and retain failed work when retries are exhausted.
Limit each batch to 25 operations. Use conditional DeleteCommand requests instead when an item must still match an expected version or state.
Delete all items without deleting the table
DynamoDB has no truncate operation. Scan full keys, split them into groups of at most 25, and retry only unprocessed deletes. This is destructive, consumes read and write capacity, and is not atomic. Stop other writers and take an appropriate backup first; a scan is not a snapshot, so concurrent writes can escape the cleanup or be deleted unexpectedly.
The projection reduces response size, not scan read capacity. Verify the table is empty after the job with writers still stopped. Recreating a disposable table is another option, but requires restoring its indexes, policies and other settings.
Run DynamoDB Local
Start an isolated, in-memory Docker instance for local tests. Data disappears when the container stops.
docker run --rm -p 127.0.0.1:8000:8000 amazon/dynamodb-local:3.3.2 -jar DynamoDBLocal.jar -inMemory -sharedDb
Use an explicit endpoint and dummy credentials only for Local:
Use this client configuration in the create-table and item examples. Local needs its own table; it does not connect to your AWS tables. It cannot reproduce every cloud behavior, including real capacity throttling. See the local setup guide and AWS Local instructions.
Query a date range
Query a timestamp sort key with BETWEEN inside one partition-key value. The bounds are inclusive; the example uses fixed-width UTC timestamp strings and reads every page.
import{ DynamoDBClient }from"@aws-sdk/client-dynamodb";import{ DynamoDBDocumentClient, QueryCommand }from"@aws-sdk/lib-dynamodb";const ddb = DynamoDBDocumentClient.from(newDynamoDBClient({}),{marshallOptions:{removeUndefinedValues:true},});let lastKey;do{const page =await ddb.send(newQueryCommand({TableName:"Orders",KeyConditionExpression:"#pk = :pk AND #sk BETWEEN :start AND :end",ExpressionAttributeNames:{"#pk":"pk","#sk":"sk"},ExpressionAttributeValues:{":pk":"CUSTOMER#123",":start":"2026-10-01T00:00:00.000Z",":end":"2026-10-31T23:59:59.999Z",},ExclusiveStartKey: lastKey,}));for(const item of page.Items ??[]) console.log(item);
lastKey = page.LastEvaluatedKey;}while(lastKey && Object.keys(lastKey).length >0);
Normalize time zones and precision before writing. A timestamp stored only as a non-key attribute cannot narrow the items read by a key condition.
Filter a nested document
A document path can filter a nested Map attribute after Query selects a partition. This example keeps items whose details.city is Lisbon; it does not search a JSON-encoded String.
Store details as a DynamoDB Map, not JSON.stringify(details). For frequent lookups across partitions, copy the city to a top-level index key attribute.
Count query results
Select COUNT returns the number of matches in one response, not the total for the entire query. Sum Count while paginating; COUNT still consumes read capacity.
With a filter, Count counts returned matches and ScannedCount counts evaluated items. Approximate table ItemCount is a different metric and is not an exact real-time count.
Handle errors deliberately
The SDK retries some transient service errors. A successful batch response can still contain unprocessed work, which needs the explicit loops shown above. Log request IDs and attempt counts from $metadata when diagnosing failures, without logging credentials or sensitive item values.
A condition failure usually means application state changed, not that the request should be blindly retried. Non-idempotent updates such as increments need an application-level retry strategy. See DynamoDB throttling and conditional writes.
Check modeled exception classes with instanceof. This example handles an expected condition conflict and propagates other failures after logging diagnostic metadata.
import{
DynamoDBClient, ConditionalCheckFailedException, DynamoDBServiceException,}from"@aws-sdk/client-dynamodb";import{ DynamoDBDocumentClient, UpdateCommand }from"@aws-sdk/lib-dynamodb";const ddb = DynamoDBDocumentClient.from(newDynamoDBClient({maxAttempts:3}));try{await ddb.send(newUpdateCommand({TableName:"Orders",Key:{pk:"CUSTOMER#123",sk:"ORDER#001"},UpdateExpression:"SET #status = :next",ConditionExpression:"#status = :expected",ExpressionAttributeNames:{"#status":"status"},ExpressionAttributeValues:{":expected":"PENDING",":next":"SHIPPED"},}));}catch(error){if(error instanceofConditionalCheckFailedException){
console.error("Order was absent or no longer pending");}else{if(error instanceofDynamoDBServiceException){
console.error(error.name, error.$metadata.requestId, error.$metadata.attempts);}throw error;}}
For transactions, handle TransactionCanceledException and inspect cancellation reasons before retrying. A transaction condition conflict is not a standalone ConditionalCheckFailedException. The SDK's error handling guide explains service exception metadata.
TypeScript notes
The same command APIs work in TypeScript. Use input types to catch misspelled request parameters, but validate returned items at runtime: DynamoDB's schema does not guarantee your application's non-key attributes.
Use an ESM-compatible TypeScript configuration and keep SDK package versions compatible. Do not assume every DynamoDB Number fits safely in a JavaScript Number. For precision-sensitive values, use the document client's NumberValue and wrapNumbers options described in the package reference.
Migrating from DocumentClient v2
AWS SDK for JavaScript v2 reached end of support on September 8, 2025. Move from aws-sdk to the modular v3 packages; remove .promise() when converting calls.
Set marshallOptions.removeUndefinedValues when that is intended
The document client takes native values such as { pk: "CUSTOMER#123" }; do not wrap them as { S: "CUSTOMER#123" }. Use native Set values for DynamoDB sets. For exact large numbers, review NumberValue and unmarshalling options instead of coercing everything into a JavaScript Number.