Building with DynamoDB: A 4-Minute AWS CLI Tutorial

 

Building with DynamoDB: A 4-Minute AWS CLI Tutorial

Learn to create a DynamoDB table, index it, and avoid common design traps — all from your terminal. This tutorial will help you understand the 10 GB item collection size limit (per partition key) that can trigger the ItemCollectionSizeLimitExceededException if your design isn’t balanced.





Step 1: Create a Table

In this step, you’ll create a DynamoDB table with a partition key (CustomerID) and a sort key (OrderID). These two attributes define how your data will be uniquely stored and accessed.

aws dynamodb create-table \
  --table-name OrdersTable \
  --attribute-definitions \
      AttributeName=CustomerID,AttributeType=S \
      AttributeName=OrderID,AttributeType=S \
  --key-schema \
      AttributeName=CustomerID,KeyType=HASH \
      AttributeName=OrderID,KeyType=RANGE \
  --billing-mode PAY_PER_REQUEST

Output:

{
    "TableDescription": {
        "TableName": "OrdersTable",
        "TableStatus": "CREATING",
        "BillingModeSummary": { "BillingMode": "PAY_PER_REQUEST" },
        "ItemCount": 0,
        "TableSizeBytes": 0
    }
}

You should see confirmation that the table is being created. The status CREATING means AWS is provisioning it.

Check the table status until it becomes ACTIVE:

aws dynamodb describe-table --table-name OrdersTable

Output:

{
    "Table": {
        "AttributeDefinitions": [
            {
                "AttributeName": "CustomerID",
                "AttributeType": "S"
            },
            {
                "AttributeName": "OrderID",
                "AttributeType": "S"
            }
        ],
        "TableName": "OrdersTable",
        "KeySchema": [
            {
                "AttributeName": "CustomerID",
                "KeyType": "HASH"
            },
            {
                "AttributeName": "OrderID",
                "KeyType": "RANGE"
            }
        ],
        "TableStatus": "ACTIVE",
        "CreationDateTime": "2025-10-16T18:23:47.104000-05:00",
        "ProvisionedThroughput": {
            "NumberOfDecreasesToday": 0,
            "ReadCapacityUnits": 0,
            "WriteCapacityUnits": 0
        },
        "TableSizeBytes": 0,
        "ItemCount": 0,
        "BillingModeSummary": {
            "BillingMode": "PAY_PER_REQUEST"
        }
    }
}

"TableStatus": "ACTIVE" confirms your table is ready for reads and writes.


Step 2: Insert Sample Data

Now, you’ll add some data to your table. Each item will represent an order for a specific customer.

aws dynamodb put-item \
  --table-name OrdersTable \
  --item '{"CustomerID": {"S": "CUST#1001"}, "OrderID": {"S": "ORD#5001"}, "Amount": {"N": "29.99"}, "Status": {"S": "SHIPPED"}}'

Output:

{}

An empty {} means the item was inserted successfully.

Insert another record.

aws dynamodb put-item \
  --table-name OrdersTable \
  --item '{"CustomerID": {"S": "CUST#1001"}, "OrderID": {"S": "ORD#5002"}, "Amount": {"N": "45.00"}, "Status": {"S": "PENDING"}}'

Output:

{}

Each successful put-item command returns an empty JSON response.


Step 3: Query by Partition Key

Here, you’ll retrieve all orders for a single customer.

aws dynamodb query \
  --table-name OrdersTable \
  --key-condition-expression "CustomerID = :cust" \
  --expression-attribute-values '{":cust":{"S":"CUST#1001"}}'

Output:

{
    "Items": [
        {
            "CustomerID": {"S": "CUST#1001"},
            "OrderID": {"S": "ORD#5001"},
            "Amount": {"N": "29.99"},
            "Status": {"S": "SHIPPED"}
        },
        {
            "CustomerID": {"S": "CUST#1001"},
            "OrderID": {"S": "ORD#5002"},
            "Amount": {"N": "45.00"},
            "Status": {"S": "PENDING"}
        }
    ],
    "Count": 2,
    "ScannedCount": 2
}

This shows both items you inserted earlier. Each object under Items represents a record in DynamoDB.


Step 4: Add a Global Secondary Index (GSI)

Now, you’ll create an index on the Status attribute so you can query orders by their shipment status. Since Status is a low-cardinality key, be aware that this design pattern can lead to hot partitions and the 10 GB item-collection limit in large-scale applications.

aws dynamodb update-table \
  --table-name OrdersTable \
  --attribute-definitions AttributeName=Status,AttributeType=S \
  --global-secondary-index-updates \
      "[{\"Create\":{\"IndexName\":\"StatusIndex\", \
                     \"KeySchema\":[{\"AttributeName\":\"Status\",\"KeyType\":\"HASH\"}], \
                     \"Projection\":{\"ProjectionType\":\"ALL\"}}}]"

Output:

{
    "TableDescription": {
        "TableName": "OrdersTable",
        "GlobalSecondaryIndexes": [
            {
                "IndexName": "StatusIndex",
                "IndexStatus": "CREATING"
            }
        ]
    }
}

You should see the index in the CREATING state. DynamoDB is now building it in the background.

Check the index status until it’s active. Run the command below every few seconds until it shows ACTIVE.

aws dynamodb describe-table --table-name OrdersTable

Output:

{
    "Table": {
        "TableName": "OrdersTable",
        "TableStatus": "ACTIVE",
        "GlobalSecondaryIndexes": [
            {
                "IndexName": "StatusIndex",
                "KeySchema": [
                    {
                        "AttributeName": "Status",
                        "KeyType": "HASH"
                    }
                ],
                "Projection": {
                    "ProjectionType": "ALL"
                },
                "IndexStatus": "ACTIVE",
                "ProvisionedThroughput": {
                    "NumberOfDecreasesToday": 0,
                    "ReadCapacityUnits": 0,
                    "WriteCapacityUnits": 0
                },
                "IndexSizeBytes": 0,
                "ItemCount": 2
            }
        ],
        "BillingModeSummary": {
            "BillingMode": "PAY_PER_REQUEST"
        }
    }
}

Here you’ll see both "TableStatus": "ACTIVE" and "IndexStatus": "ACTIVE", confirming that the GSI is fully built and ready to query.


Step 5: Query by Status Using the GSI

Now that the index is ready, you can query all shipped orders by status.

aws dynamodb query \
  --table-name OrdersTable \
  --index-name StatusIndex \
  --key-condition-expression "Status = :s" \
  --expression-attribute-values '{":s":{"S":"SHIPPED"}}'

Output:

{
    "Items": [
        {
            "CustomerID": {"S": "CUST#1001"},
            "OrderID": {"S": "ORD#5001"},
            "Amount": {"N": "29.99"},
            "Status": {"S": "SHIPPED"}
        }
    ],
    "Count": 1,
    "ScannedCount": 1
}

You should see only the items that match your query condition (Status = SHIPPED).


Step 6: Clean Up

Finally, you’ll delete your table to avoid unnecessary charges.

aws dynamodb delete-table --table-name OrdersTable

Output:

{
    "TableDescription": {
        "TableName": "OrdersTable",
        "TableStatus": "DELETING"
    }
}

DELETING confirms the removal process has started.

Verify that the table no longer exists.

aws dynamodb list-tables

Output:

{
    "TableNames": []
}

An empty TableNames list means your table was successfully deleted.


Wrap-Up

You’ve just built a working DynamoDB table, queried it by partition key and GSI, and observed how indexes operate behind the scenes. This workflow helps reinforce both DynamoDB’s flexibility and its constraints — including the 10 GB item collection size limit that underscores why good data modeling matters from the start.

To avoid hitting that 10 GB wall in production, consider designing composite keys — for example, combining Status with order date or region — to increase key cardinality and ensure better data distribution.


Aaron Rose is a software engineer and technology writer at tech-reader.blog and the author of Think Like a Genius.

Comments

Popular posts from this blog

The New ChatGPT Reason Feature: What It Is and Why You Should Use It

Raspberry Pi Connect vs. RealVNC: A Comprehensive Comparison

Insight: The Great Minimal OS Showdown—DietPi vs Raspberry Pi OS Lite