AWS DynamoDB DocumentClient & Node.js - Complete Cheat Sheet
Tags: serverless, typescript, javascript, node
Published: 2021-05-30 15:13:28

---
title: AWS DynamoDB DocumentClient & Node.js - Complete Cheat Sheet
published: true
description: This cheat sheet covers the most important DynamoDB Node.js DocumentClient query examples that you can copy-paste-tweak for your next DynamoDB JavaScript project.
tags: serverless,typescript,javascript,nodejs
cover_image: https://5618scdg33.execute-api.us-east-1.amazonaws.com/dev/screenshot?template=index.html&fontSize=3.2em&title=DynamoDB%20and%20Node.js%20-%20Complete%20Cheat%20Sheet
canonical_url: https://dynobase.dev/dynamodb-nodejs/
---

## DynamoDB Node.js Query Examples

This cheat sheet should help you how to perform variefy of operations starting from simple queries ending with complex transactions using AWS DynamoDB DocumentClient and Node.js.

There are two basic ways to interact with DynamoDB tables from Node.js applications:

- Class `AWS.DynamoDB` from AWS SDK for JavaScript/Typescript
- `AWS.DynamoDB.DocumentClient` which simplifies working with DynamoDB items by abstracting away DynamoDB Types and converting responses to native JS

This cheat sheet will mostly focus on `DocumentClient` but some of the operations like creating tables must be run using classical DynamoDB service.

If you're looking for similar cheat sheet but for Python, [you can find it here](https://dynobase.dev/dynamodb-python-with-boto3/)

**Bonus:** AWS recently announced Node.js AWS SDK v3. I wrote a tutorial on [how to use new SDK V3, especially in the DynamoDB context](https://www.rwilinski.me/blog/migrating-to-aws-sdk-v3/)

## Table of Contents

- [Setting up](#setup)
- [Create Table](#Create-table)
- [Delete Table](#delete-table)
- [List Tables](#list-tables)
- [Get All Items / Scan](#scan)
- [Get Item](#get-item)
- [Batch Get Item](#batch-get-item)
- [Put Item](#put-item)
- [Batch Write Item](#batch-write-item)
- [Query for a Set of Items](#query-items)
- [Query an Index](#query-index)
- [Update Item](#update-item)
- [Conditionally Update Item](#conditionally-update-item)
- [Increment Item Attribute](#increment-item-attribute)
- [Delete Item](#delete-item)
- [Delete All Items](#delete-all-items)
- [Simple Transaction](#simple-transaction)
- [Read Transaction](#read-transaction)
- [Query with Sorting](#query-with-sorting)
- [Query Pagination](#query-pagination)
- [Run DynamoDB Local](#dynamodb-local)

### <a id="setup"></a>Setup

Setting up your Node.js application to work with DynamoDB is fairly easy. First, make sure that `aws-sdk` is installed, either by running `yarn add aws-sdk` or `npm i aws-sdk --save`. Then, paste following piece of code:

```javascript
const AWS = require("aws-sdk") // Or use `import` syntax for Typescript and newer ES versions

const dynamoDB = new AWS.DynamoDB({
  region: "us-east-1", // If not set, will get from ~/.aws directory or environment variable
  // and rest of properties
})

// Or

const documentClient = new AWS.DynamoDB.DocumentClient({
  region: "us-east-1",
  // and rest of properties
})
```

As one of the Node.js best practices is to avoid [callback hell](http://callbackhell.com/), we'll be ending up all of our SDK calls with `.promise()` call in order to get Promises returned from SDK. It will also allow us to use `async/await` syntax which makes our programs much more readable.

Keep in mind that using access and secret keys is against best security practices, and you should instead use IAM roles/policies to interact with DynamoDB. This code, if ran on Lamba function or EC2 instance, will automatically use IAM Role attached to it.

### <a id="Create-table"></a>Create table

DynamoDB structures data in tables, so if you want to save some data to DynamoDB, first you need to create a table. You can do that using AWS Console, AWS CLI or using AWS-SDK for Javascript, like this:

```javascript
const AWS = require("aws-sdk")
const dynamoDB = new AWS.DynamoDB({ region: "us-east-1" })

dynamoDB
  .createTable({
    AttributeDefinitions: [
      {
        AttributeName: "id",
        AttributeType: "S",
      },
    ],
    KeySchema: [
      {
        AttributeName: "id",
        KeyType: "HASH",
      },
    ],
    BillingMode: "PAY_PER_REQUEST",
    TableName: "my-table",
  })
  .promise()
  .then(data => console.log("Success!", data))
  .catch(console.error)
```

After this call resolves, it does not necessarily mean that table status is `ACTIVE` and it's is ready for read and write operations. Before start manipulating items in it, we should check if it's in `ACTIVE` state first using `describeTable` function ran every 5 seconds until it is ready:

```javascript
const backoffInterval = 5000 // 5 seconds

const waitForTable = TableName =>
  dynamoDB
    .describeTable({ TableName })
    .promise()
    .then(data => {
      if (data.Table.TableStatus !== "ACTIVE") {
        console.log(
          `Table status: ${data.Table.TableStatus}, retrying in ${backoffInterval}ms...`
        )
        return new Promise(resolve => {
          setTimeout(() => waitForTable().then(resolve), backoffInterval)
        })
      } else {
        return
      }
    })
    .catch(error => {
      console.warn(
        `Table not found! Error below. Retrying in ${backoffInterval} ms...`,
        error
      )

      return new Promise(resolve => {
        setTimeout(() => waitForTable().then(resolve), backoffInterval)
      })
    })

waitForTable("my-table").then(() => console.log(`my-table is ready!`))
```

### <a id="delete-table"></a>Delete table

If you changed your mind and need to remove DynamoDB table, don't worry, it's simple:

```javascript
const AWS = require("aws-sdk")
const dynamoDB = new AWS.DynamoDB({ region: "us-east-1" })

dynamoDB
  .deleteTable({
    TableName: "my-table",
  })
  .promise()
  .then(() => console.log("Table has been deleted"))
  .catch(console.error)
```

Keep in mind that [Dynobase is capable to removing tables too](https://dynobase.dev/delete-dynamodb-table/).

### <a id="list-tables"></a>List Tables

If you want to check what tables are available at your disposal in current region, use `listTables` call. Keep in mind that if selected region has more than 100 tables you'll have to [paginate through them](#query-pagination) to fetch a complete list.

```javascript
const AWS = require("aws-sdk")
const dynamoDB = new AWS.DynamoDB({ region: "us-east-1" })

dynamoDB
  .listTables()
  .promise()
  .then(() => console.log("Table has been deleted"))
  .catch(console.error)
```

### <a id="scan"></a>Get All Items / Scan in DynamoDB

After our table is provisioned and it's in `ACTIVE` state, first thing that we probably would like to do is get all items in it aka use (DynamoDB [Scan](https://dynobase.dev/dynamodb-scan/) operation):

```javascript
const AWS = require("aws-sdk")
const dynamoDB = new AWS.DynamoDB.DocumentClient()

dynamoDB
  .scan({
    TableName: "my-table",
  })
  .promise()
  .then(data => console.log(data.Items))
  .catch(console.error)
```

If you want to narrow your search results, use `FilterExpressions` combined with `ExpressionAttributeNames` object like so:

```javascript
const AWS = require("aws-sdk")
const dynamoDB = new AWS.DynamoDB.DocumentClient({ region: "us-east-1" })

dynamoDB
  .scan({
    TableName: "my-table",
    FilterExpression:
      "attribute_not_exists(deletedAt) AND contains(firstName, :firstName)",
    ExpressionAttributeValues: {
      ":firstName": "John",
    },
  })
  .promise()
  .then(data => console.log(data.Items))
  .catch(console.error)
```

You can find full reference how to write `FilterExpression`s in [this post by Alex Debrie](https://www.alexdebrie.com/posts/dynamodb-filter-expressions/).

The snippet above will in fact return all the items in the table under one condition - you have less than 1MB of data inside it. If your table is bigger than that, you'll have to run [Scan](https://dynobase.dev/dynamodb-scan) command a few times in a loop using [pagination](#query-pagination).

### <a id="get-item"></a>Get Item

If you know the exact Partition Key (and Sort Key if using composite key) of the item that you want to retrieve from the DynamoDB table, you can use `get` operation:

```javascript
const AWS = require("aws-sdk")
AWS.config.update({ region: "us-east-1" })
const dynamoDB = new AWS.DynamoDB.DocumentClient()

dynamoDB
  .get({
    TableName: "my-table",
    Key: {
      id: "123", // id is the Partition Key, '123' is the value of it
    },
  })
  .promise()
  .then(data => console.log(data.Item))
  .catch(console.error)
```

### <a id="batch-get-item"></a>Batch Get Item

`DocumentClient` is also capable of running bunch of `get` operations in a single call to the DynamoDB service:

```javascript
const AWS = require("aws-sdk")
AWS.config.update({ region: "us-east-1" })
const dynamoDB = new AWS.DynamoDB.DocumentClient()

dynamoDB
  .batchGet({
    RequestItems: {
      "my-table": {
        Keys: [
          {
            id: "123",
          },
          {
            id: "124",
          },
        ],
      },
      "other-table": {
        Keys: [
          {
            id: "abc",
          },
          {
            id: "abd",
          },
        ],
      },
    },
  })
  .promise()
  .then(data => console.log(data.Responses))
  .catch(console.error)
```

As you can see, the `RequestItems` objects can accept multiple table names and can fetch multiple items from multiple tables in a single call. Keep in mind that number of items retrieved using `batchGet` is limited to 100 items or 16MB of data.

Moreover, if you exceed table capacity, this call will return `UnprocessedKeys` attribute containing a map of keys which weren't fetched.

### <a id="put-item"></a>Put Item aka Write

`put` operation creates a new item, or replaces an old item with a new item if it's using the same key(s):

```javascript
const AWS = require("aws-sdk")
const dynamoDB = new AWS.DynamoDB.DocumentClient({ region: "us-east-1" })

dynamoDB
  .put({
    Item: {
      id: "12346",
      name: "John Doe",
      email: "john@doe.io",
    },
    TableName: "my-table",
  })
  .promise()
  .then(data => console.log(data.Attributes))
  .catch(console.error)
```

### <a id="batch-write-item"></a>Batch Write / Put Item

If you need to insert, update or delete multiple items in a single API call, use `batchWrite` operation. It bundles multiple database requests against multiple tables into a single SDK call. It decreases amount of network calls needed to be made, reduces the overall latency and makes your application faster.

Example request deleting one item with key `id = 123` and putting another to the same table:

```javascript
const AWS = require("aws-sdk")
const dynamoDB = new AWS.DynamoDB.DocumentClient({ region: "us-east-1" })

dynamoDB
  .batchWrite({
    RequestItems: {
      MyTable: [
        {
          DeleteRequest: {
            Key: { id: "123" },
          },
        },
        {
          PutRequest: {
            Item: {
              id: "234",
              name: "dynobase",
              email: "dynobase@dynobase.dev",
              description: "Professional DynamoDB Client",
            },
          },
        },
      ],
    },
  })
  .promise()
  .then(data => console.log(data.Attributes))
  .catch(console.error)
```

If you want to write into multiple tables at once, simply specify these tables inside `RequestItems` object like this:

```javascript
const AWS = require("aws-sdk")
const dynamoDB = new AWS.DynamoDB.DocumentClient({ region: "us-east-1" })

dynamoDB
  .batchWrite({
    RequestItems: {
      TableOne: [
        {
          DeleteRequest: {
            Key: { id: "123" },
          },
        },
      ],
      TableTwo: [
        {
          PutRequest: {
            Item: {
              id: "234",
              name: "dynobase",
              email: "dynobase@dynobase.dev",
              description: "Professional DynamoDB Client",
            },
          },
        },
      ],
    },
  })
  .promise()
  .then(data => console.log(data.Attributes))
  .catch(console.error)
```

If you are curious about performance of `batchWrite`, head to [dynamodb-performance-testing](https://github.com/alexdebrie/dynamodb-performance-testing) repo by Alex DeBrie.

### <a id="query-items"></a>Query for a Set of Items

If your table has composite key (which is the best practice), in order to get a collection of items sharing the same [Parition Key](https://dynobase.dev/dynamodb-keys/), use `Query` method. It also allows to use multiple operators for SortKey such as `begins_with` or mathematical ones like `>`, `=`, `>=` and so on.

```javascript
const AWS = require('aws-sdk');
const dynamoDB = new AWS.DynamoDB.DocumentClient({region:'us-east-1'});

dynamoDB
  .query({
    TableName: 'my-table',
    KeyConditionExpression: 'id = :hashKey and date > :rangeKey'
    ExpressionAttributeValues: {
      ':hashKey': '123',
      ':rangeKey': 20150101
    }
  })
  .promise()
  .then(data => console.log(data.Items))
  .catch(console.error);
```

Keep in mind that `Query` can return up to 1MB of data and you can also use `FilterExpression`s here to narrow the results on non-key attributes.

If you don't know how to construct your Query and its attributes, head to our [DynamoDB Query Builder](https://dynobase.dev/dynamodb-query-builder/) which will generate code for you. You can also use [Query Code Generation](https://dynobase.dev/dynamodb-query/) feature inside Dynobase.


### <a id="simple-transaction"></a>Simple Transaction

DynamoDB also support [transactions](https://dynobase.dev/dynamodb-transactions/) - they allow to run multiple write operations atomically meaning that either all of operations are executed succesfully or none of them. It is especially useful when dealing with applications where data integrity is essential, e.g. in e-commerce - adding an item to a cart and decrementing count of items still available to buy.

Such flow should:

- Should happen atomically - these two operations should be treated as one, we don't want to have a single moment in time where there's a discrepancy in items count
- Should succeed only if count of items available to buy is greated than zero

Described flow can be modelled in Javascript & DocumentClient like this:

```javascript
const AWS = require("aws-sdk")
const dynamoDB = new AWS.DynamoDB.DocumentClient({ region:'us-east-1' })

await dynamoDB.transactWrite({
  TransactItems: [
    {
      Put: { // Add item to cart
        Item: {
          id: '1',
          count: '1'
        }
        TableName: "CartTable",
      },
    },
    {
      Update: { // decrement count of items available to buy only if the count is greater than zero
        ConditionExpression: "#count > 0",
        ExpressionAttributeNames: { "#count": "count" },
        ExpressionAttributeValues: {
          ":value": 1,
        },
        Key: {
          id: '123',
        }
        TableName: "ItemsTable",
        UpdateExpression: "SET #count = :count - :value",
      },
    },
  ],
}).promise();
```

If you want to learn more about transactions, head to [our DynamoDB Transactions Guide](https://dynobase.dev/dynamodb-transactions/).

### <a id="read-transaction"></a>Read Transaction

[Transactions](https://dynobase.dev/dynamodb-transactions/) can be also used for reading data atomically. Like in [`batchGet`](#batch-get), you can retrieve the data from multiple tables in a single call:

```javascript
const AWS = require("aws-sdk")
const dynamoDB = new AWS.DynamoDB.DocumentClient({ region: "us-east-1" })

await dynamoDB
  .transactGet({
    TransactItems: [
      {
        Get: {
          TableName: "TableOne",
          Key: {
            HashKey: "abcd",
          },
        },
      },
      {
        Get: {
          TableName: "TableTwo",
          Key: {
            HashKey: "1234",
          },
        },
      },
    ],
  })
  .promise()
```

If you want to learn more about transactions, head to [our DynamoDB Transactions Guide](https://dynobase.dev/dynamodb-transactions/).

### <a id="query-with-sorting"></a>Query with Sorting

Unfortunately, DynamoDB offers only one way of sorting the results on the database side - using the sort key. If your table does not have one, your sorting capabilities are limited to sorting items in application code after fetching the results. However, if you need to sort DynamoDB results on sort key descending or ascending, you can use following syntax:

```javascript
const AWS = require('aws-sdk');
const dynamoDB = new AWS.DynamoDB.DocumentClient({ region: 'us-east-1' });

dynamoDB
  .query({
    TableName: 'my-table',
    IndexName: 'Index', // Main one
    KeyConditionExpression: 'id = :hashKey and date > :rangeKey'
    ExpressionAttributeValues: {
      ':hashKey': '123',
      ':rangeKey': 20150101
    },
    ScanIndexForward: true // true or false to sort by "date" Sort/Range key ascending or descending
  })
  .promise()
  .then(data => console.log(data.Items))
  .catch(console.error);
```

### <a id="query-pagination"></a>Query (and Scan) DynamoDB Pagination

Both [Query](https://dynobase.dev/dynamodb-query/) and [Scan](https://dynobase.dev/dynamodb-scan/) operations return results with up to 1MB of items. If you need to fetch more records, you need to invoke a second call to fetch the next page of results. If `LastEvaluatedKey` is present in response object, this table has more items like requested and another call with `ExclusiveStartKey` should be sent to fetch more of them:

```javascript
const getAll = async () => {
  let result, accumulated, ExclusiveStartKey;

  do {
    result = await DynamoDB.query({
      TableName: argv.table,
      ExclusiveStartKey,
      Limit: 100,
      KeyConditionExpression: 'id = :hashKey and date > :rangeKey'
      ExpressionAttributeValues: {
        ':hashKey': '123',
        ':rangeKey': 20150101
      },
    }).promise();

    ExclusiveStartKey = result.LastEvaluatedKey;
    accumulated = [...accumulated, ...result.Items];
  } while (result.Items.length || result.LastEvaluatedKey);

  return accumulated;
};

getAll()
  .then(console.log)
  .catch(console.error);
```

### <a id="update-item"></a>Update Item

DynamoDB `update` operation in Node.js consists of two main parts:

- Part **which** item to update (`Key`), similar to [get](#get)
- Part **what** in the selected item should be updated (`UpdateExpression` and `ExpressionAttributeValues`)

```javascript
const AWS = require("aws-sdk")
AWS.config.update({ region: "us-east-1" })
const dynamoDB = new AWS.DynamoDB.DocumentClient()

dynamoDB
  .update({
    TableName: "my-table",
    Key: {
      id: "123",
    },
    UpdateExpression: `set firstName = :firstName`,
    ExpressionAttributeValues: {
      ":firstName": "John McNewname",
    },
  })
  .promise()
  .then(data => console.log(data.Attributes))
  .catch(console.error)
```

### <a id="conditionally-update-item"></a>Conditionally Update Item

Sometimes we want to update our record only if some condition is met, e.g. item is not soft-deleted (does not have `deletedAt` attribute set). To do that, use `ConditionExpression` which has similar syntax to the `FilterExpression`:

```javascript
const AWS = require("aws-sdk")
AWS.config.update({ region: "us-east-1" })
const dynamoDB = new AWS.DynamoDB.DocumentClient()

dynamoDB
  .update({
    TableName: "my-table",
    Key: {
      id: "123",
    },
    UpdateExpression: `set firstName = :firstName`,
    ExpressionAttributeValues: {
      ":firstName": "John McNewname",
      ":company": "Apple",
    },
    ConditionExpression: `attribute_not_exists(deletedAt) and company = :company`,
  })
  .promise()
  .then(data => console.log(data.Attributes))
  .catch(console.error)
```

In this example the `name` attribute of the record with partition key `id = 123` in table `my-table` will be only updated if this item does not have attribute `deletedAt` and its attribute `company` has value `Apple`.

### <a id="increment-item-attribute"></a>Increment Item Attribute

Incrementing a Number value in DynamoDB item can be achieved in two ways:

1. Get item, update the value in the application code and send a `put` request back to DDB overwriting item
2. Using `update` operation

While it might be tempting to use first method because Update syntax is unfriendly, I strongly recommend using second one because of the fact it's much faster (requires only one request) and atomic:

```javascript
const AWS = require("aws-sdk")
AWS.config.update({ region: "us-east-1" })
const dynamoDB = new AWS.DynamoDB.DocumentClient()

dynamoDB
  .update({
    TableName: "my-table",
    Key: {
      id: "123",
    },
    UpdateExpression: `set score = :score + :value`,
    ExpressionAttributeValues: {
      ":value": 1,
    },
  })
  .promise()
  .then(data => console.log(data.Attributes))
  .catch(console.error)
```

In this example the `score` attribute of the record with partition key `id = 123` in table `my-table` will incremented by one. Of course, you can use other mathematical operators too.

### <a id="delete-item"></a>Delete Item

Removing single item from table is very similar to [Get Item](#get-item) operation. The parameters of the call are actually exactly the same, the only difference is that we call `delete` instead of `get`:

```javascript
const AWS = require("aws-sdk")
const dynamoDB = new AWS.DynamoDB.DocumentClient({ region: "us-east-1" })

dynamoDB
  .delete({
    TableName: "my-table",
    Key: {
      id: "123", // id is the Partition Key, '123' is the value of it
    },
  })
  .promise()
  .then(data => console.log(data.Attributes))
  .catch(console.error)
```

### <a id="delete-all-items"></a>Delete All Items

Unfortunately, there's no easy way to delete all items from DynamoDB just like in SQL-based databases by using `DELETE FROM my-table;`. To achieve the same result in DynamoDB, you need to [query/scan](dynamodb-query-vs-scan) to get all the items in a table using [pagination](#query-pagination) until all items are scanned and then perform `delete` operation one-by-one on each record.

```javascript
const truncateTable = async () => {
  let ExclusiveStartKey, result

  do {
    result = await DynamoDB.scan({
      TableName: argv.table,
      ExclusiveStartKey,
    }).promise()

    ExclusiveStartKey = result.LastEvaluatedKey

    console.log(`Found ${result.Items.length} Items, removing...`)

    if (result.Items.length > 0) {
      await Promise.all(
        result.Items.map(async item =>
          DynamoDB.delete({
            TableName: argv.table,
            Key: {
              pk: item.pk,
              sk: item.sk,
            },
          }).promise()
        )
      )
    }
  } while (result.Items.length || result.LastEvaluatedKey)
}

truncateTable()
  .then(() => console.log("Done!"))
  .catch(console.error)
```

If you don't want to run this script on your own (it might be really time consuming), truncating table is [possible just with few clicks using Dynobase](https://dynobase.dev/dynamodb-delete-all-items/).

### <a id="dynamodb-local"></a>Run DynamoDB Local

If you need to use DynamoDB offline locally, you can use [DynamoDB local](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.html) distributed by AWS or DynamoDB from [Localstack](https://github.com/localstack/localstack). Connecting to it is as easy as changing the `endpoint` parameter `DynamoDB` or `DocumentClient` constructor:

```javascript
const AWS = require("aws-sdk")

const dynamoDB = new AWS.DynamoDB({
  // or DocumentClient
  endpoint: "http://localhost:8000",
})
```

If you want to see your local tables and data in them, you can use [Dynobase to query and modify items in offline tables](https://dynobase.dev/dynamodb-local-admin-gui/).

Learn more about [running DynamoDB locally](https://dynobase.dev/run-dynamodb-locally/).

### Bonus!

If you're looking for [Serverless Framework](https://www.serverless.com/) based CRUD project which uses other AWS Lambda and DynamoDB best practices with Node.js, you can find one on our [Github](https://github.com/Dynobase/serverless-dynamodb-api-boilerplate).