DynamoDB Operations

This is a brief guide of examples about how to use the AWS SDK (in Node.js) to perform operations in DynamoDB

Cover image

Introduction

DynamoDB as we seen before is the high performance NoSQL Database platform of AWS and in my opinion is pretty amazing, however it takes time to get used to work with it, especially if you're coming from years of working with relational schemes such as SQL, so I hope this helps. If you didn't check my Serverless Series - Part 2 I'll recommend you to take a look at least in the first part to understand the main principles of designing NoSQL scheme specifically in DynamoDB, also please refer to the official AWS Docs of this library for more information.

Prerequisites

Model

The models that we're going to use in this post are the ones that we used in the Serverless Series - Part 2

Put Item

In DynamoDB we don't have directly an insert operation, instead we have a put operation that it's a write operation that sets a record in the database, be careful with this operation because you could overwrite records if its primary key it's the same, to prevent this we use a condition expression that validates if the primary key previously exists. In the example below, we can observe the following parameters:

  • TableName: The name of your DynamoDB table.
  • Item: The data that we're going to insert, must have a valid primary key.
  • ConditionExpression(optional): In this example, this prevents the overwriting of exiting items.
  • ReturnConsumedCapacity(optional): This parameter is to return how many capacity units we used in the operations.
const AWS = require('aws-sdk')
let dynamodb = new AWS.DynamoDB.DocumentClient({ region: 'us-east-1'})

let params = {
    TableName: 'ServerlessSeries',
    Item: {
        pk: "marco.mercado@gmail.com",
        sk: "user",
        first_name: "Marco",
        last_name: "Mercado"
    },
    ConditionExpression: "attribute_not_exists(pk)",
    ReturnConsumedCapacity: 'TOTAL'
}

dynamodb.put(params, (err, data) => {
    if (err) console.log(err)
    else console.log(data)
})

Update Item

The update item operation, it's a little bit more complex than the put operation, the main differences are that unlike the put operation, we need to target an existing item in order to update its information. In the example below, we can observe the following parameters:

  • Key: The primary key of the item.
  • UpdateExpression: The attributes that we're going to update and it's respective values.
  • ExpressionAttributeNames: This one, it's a little bit tricky, because it's a declarative parameter of the attributes that we're going to update, this is used in the update expression to avoid having issues with reserved words and any other issue related with the interpretation of this expression.
  • ExpressionAttributeValues: This one, it's like the ExpressionAttributeNames but instead of declaring the attribute names that we're going to use in the update expression, we declare the values.
const AWS = require('aws-sdk')
let dynamodb = new AWS.DynamoDB.DocumentClient({ region: 'us-east-1'})

let params = {
    TableName: 'ServerlessSeries',
    Key: {
        pk: 'marco.mercado@mail.com',
        sk: 'user'
    },
    ExpressionAttributeNames: {
        '#last_name': 'last_name'
    },
    ExpressionAttributeValues: {
        ':last_name': 'Uchiha'
    },
    UpdateExpression: 'SET #last_name = :last_name',
    ReturnConsumedCapacity: 'TOTAL'
}

dynamodb.update(params, (err, data) => {
    if (err) console.log(err)
    else console.log(data)
})

Scan

This is the most simple operation, but also the most ineffective one, it's a complete scan of the database that can filter data, but have some constrains, in terms of performance, it's a lot more slower than the query operation and in terms of usability it can only scan 1mb of data if we scan more of that we need to run it multiple times indicating where we got stopped every time.

const AWS = require('aws-sdk')
let dynamodb = new AWS.DynamoDB.DocumentClient({ region: 'us-east-1'})

let params = {
    TableName: 'ServerlessSeries',
    ReturnConsumedCapacity: 'TOTAL'
}

dynamodb.scan(params, (err, data) => {
    if (err) console.log(err)
    else console.log(data.Items)
})

Get Item

In order to return a single item we use the get operation, we just need the primary key as we've seen in the update item operation.

const AWS = require('aws-sdk')
let dynamodb = new AWS.DynamoDB.DocumentClient({ region: 'us-east-1'})

let params = {
    TableName: 'ServerlessSeries',
    Key: {
        pk: 'marco.mercado@mail.com',
        sk: 'user'
    },
    ReturnConsumedCapacity: 'TOTAL'
}

dynamodb.get(params, (err, data) => {
    if (err) console.log(err)
    else console.log(data.Item)
})

Delete Item

Delete item it's a pretty simple operation quite similar to the get operation.

const AWS = require('aws-sdk')
let dynamodb = new AWS.DynamoDB.DocumentClient({ region: 'us-east-1'})

let params = {
    TableName: 'ServerlessSeries',
    Key: {
        pk: 'marco.mercado@mail.com',
        sk: 'user'
    },
    ReturnConsumedCapacity: 'TOTAL'
}

dynamodb.delete(params, (err, data) => {
    if (err) console.log(err)
    else console.log(data)
})

Transact Write

This is in my opinion the most complex one, although with a little practice, it's pretty simple as well, the way it works is that we can send up to 25 items in one transaction and if one of those operations fails the transaction will rollback and because of that every operation of a transaction consumes the double of capacity units. In the example, below we can observe the following parameters:

  • TransactItems: This is a list of transactions to perform, every write transaction depending if is update, put or delete have the same parameters as its individual transaction.
const AWS = require('aws-sdk')
let dynamodb = new AWS.DynamoDB.DocumentClient({ region: 'us-east-1' })

let params = {
    ReturnConsumedCapacity: 'TOTAL',
    TransactItems: [
        {
            Put: {
                TableName: 'ServerlessSeries',
                Item: {
                    pk: 'T001',
                    sk: 'task',
                    task_title: 'Clean the kitchen',
                    task_description: 'The assignment is to clean the kitchen this includes wash the dishes, clean the stove, sweep and mop the floor',
                }
            },
        },
        {
            Put: {
                TableName: 'ServerlessSeries',
                Item: {
                    pk: 'T002',
                    sk: 'task',
                    task_title: 'Wash the car',
                    task_description: 'The car should look like new',
                }
            },
        },
        {
            Put: {
                TableName: 'ServerlessSeries',
                Item: {
                    pk: 'marco.mercado@mail.com',
                    sk: 'task#T001',
                    assigned_on: new Date().toISOString(),
                    assignment_status: 'IN_PROGRESS'
                }
            },
        },
        {
            Put: {
                TableName: 'ServerlessSeries',
                Item: {
                    pk: 'marco.mercado@mail.com',
                    sk: 'task#T002',
                    assigned_on: new Date().toISOString(),
                    assignment_status: 'FINISHED',
                    finished_at: new Date().toISOString()
                }
            }
        }
    ]
}

dynamodb.transactWrite(params, (err, data) => {
    if (err) console.log(err)
    else console.log(data)
})

Query

Last but not least the query operation is the most important one in my opinion and this is because is your principal concern when you're designing your DynamoDB table, if you don't take in count how the query operation works and the business requirement that you're looking to meet you can easily hate DynamoDB. In the example below, we can observe the following parameters:

  • KeyConditionExpression: As I mentioned before the query operation has a really important restriction to take in count and that restriction is in this parameter, in order to take advantage of the DynamoDB performance you must indicate the partition key that you're aiming for, otherwise you'll need to make secondary indexes or deal with the scan operation.
const AWS = require('aws-sdk')
let dynamodb = new AWS.DynamoDB.DocumentClient({ region: 'us-east-1'})

let params = {
    TableName: 'ServerlessSeries',
    ReturnConsumedCapacity: 'TOTAL',
    ExpressionAttributeNames: {
        '#sk': 'sk',
        '#pk': 'pk'
    },
    ExpressionAttributeValues: {
        ':sk': 'task#',
        ':pk': 'marco.mercado@mail.com'
    },
    KeyConditionExpression: '#pk = :pk and begins_with(#sk, :sk)'
}

dynamodb.query(params, (err, data) => {
    if (err) console.log(err)
    else console.log(data.Items)
})

Final Comments

There are a couple more operations in DynamoDB such batch (that basically is like a transaction without the rollback if one or more operations fail), but I hope that this post can help you. \ \ Github