{
    "componentChunkName": "component---src-templates-mdx-content-page-js",
    "path": "/dynamodb-tutorial-for-beginners/",
    "result": {"data":{"mdx":{"body":"var _excluded = [\"components\"];\n\nfunction _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {\n  \"path\": \"/dynamodb-tutorial-for-beginners/\",\n  \"publishedDate\": \"2026-10-23\",\n  \"updatedDate\": \"2026-10-23\",\n  \"metaTitle\": \"DynamoDB Tutorial for Beginners: Build an Orders Table\",\n  \"metaDescription\": \"Build a DynamoDB orders table in 30-45 minutes using Node.js or Python. Run locally, query keys and a GSI, prevent overwrites, and prepare for AWS.\",\n  \"title\": \"DynamoDB tutorial for beginners: build an orders table\",\n  \"authorName\": \"Rafal Wilinski\",\n  \"authorLink\": \"https://x.com/rafalwilinski\",\n  \"authorImage\": \"/images/authors/rafal-wilinski.jpg\",\n  \"image\": \"/images/posts/dynamodb-tutorial-for-beginners/social.png\"\n};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, _excluded);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"This DynamoDB tutorial builds a customer-orders table in 30-45 minutes using DynamoDB Local, with no AWS account required. You will write, read, query, update and delete orders, add a global secondary index (GSI), and prevent accidental overwrites with a conditional write. Every database step has an AWS SDK for JavaScript v3 example and a Python boto3 equivalent, with outputs from running both against the local database.\"), mdx(\"h2\", null, \"What will you build?\"), mdx(\"p\", null, \"A small order-management data layer with three access patterns:\"), mdx(\"table\", null, mdx(\"thead\", {\n    parentName: \"table\"\n  }, mdx(\"tr\", {\n    parentName: \"thead\"\n  }, mdx(\"th\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Application question\"), mdx(\"th\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"DynamoDB operation\"), mdx(\"th\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Key used\"))), mdx(\"tbody\", {\n    parentName: \"table\"\n  }, mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"What is this customer's specific order?\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, mdx(\"inlineCode\", {\n    parentName: \"td\"\n  }, \"GetItem\")), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Customer ID and order ID\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Which orders did this customer place in October?\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, mdx(\"inlineCode\", {\n    parentName: \"td\"\n  }, \"Query\"), \" on the table\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Customer ID, plus an order-ID prefix\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Which orders still need shipping, across customers?\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, mdx(\"inlineCode\", {\n    parentName: \"td\"\n  }, \"Query\"), \" on a GSI\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Status, ordered by order ID\")))), mdx(\"p\", null, \"The project is a set of runnable scripts, not a web application. Choose \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"JavaScript or Python\"), \" and follow that language throughout. The two versions use the same table; to try the other language, finish step 9 first, then start again at step 2 while the container is still running.\"), mdx(\"p\", null, \"Allow about 5 minutes for the concepts, 5-10 for setup, 15-20 for operations, and 5-10 for cleanup and the AWS transition. Installing Docker for the first time can take longer.\"), mdx(\"p\", null, mdx(\"img\", {\n    parentName: \"p\",\n    \"src\": \"/images/posts/dynamodb-tutorial-for-beginners/tutorial-map.svg\",\n    \"alt\": \"The tutorial moves from local setup and key design through item operations, index queries, conditional writes, cleanup and AWS configuration.\"\n  })), mdx(\"h2\", null, \"DynamoDB concepts in five minutes\"), mdx(\"p\", null, \"A \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"table\"), \" contains \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"items\"), \". An item is a record composed of named \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"attributes\"), \", such as \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"customerId\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"status\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"totalCents\"), \". Items can have different non-key attributes; you do not declare every field when creating a table.\"), mdx(\"p\", null, \"Our table, \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"BeginnerOrders\"), \", has a composite primary key:\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Partition key: \", mdx(\"inlineCode\", {\n    parentName: \"strong\"\n  }, \"customerId\"), \".\"), \" Orders for \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"C001\"), \" share this value. A table query must name a specific customer.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Sort key: \", mdx(\"inlineCode\", {\n    parentName: \"strong\"\n  }, \"orderId\"), \".\"), \" A value such as \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"2026-10-01#1001\"), \" contains a date and an order identifier. Within one customer, results are ordered by this value.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Primary key: both together.\"), \" Two items cannot have the same customer ID and order ID. A different order for the same customer is a different item.\")), mdx(\"p\", null, \"The date prefix makes October queries possible. DynamoDB sorts String keys by UTF-8 bytes, so consistently formatted \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"YYYY-MM-DD\"), \" dates sort chronologically. The suffix distinguishes orders on the same day; it does not sort them by time of day. The sample uses fixed dates so its output stays reproducible.\"), mdx(\"p\", null, \"A \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"GSI\"), \" provides another way to query the same data. We will add \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"StatusIndex\"), \", with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"status\"), \" as its partition key and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"orderId\"), \" as its sort key. DynamoDB maintains the index when table items change. You write to the table, not directly to the index.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Capacity mode\"), \" controls billing and throughput in AWS. On-demand (\", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PAY_PER_REQUEST\"), \") charges for read and write requests without asking you to provision capacity. Provisioned mode reserves read and write capacity units, which you manage directly or with auto scaling. This tutorial uses on-demand; \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"/dynamodb-on-demand-vs-provisioned-scaling/\"\n  }, \"compare the capacity modes\"), \" before choosing for a sustained workload. DynamoDB Local does not charge for requests or reproduce production capacity behavior.\"), mdx(\"p\", null, \"These are DynamoDB's \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.CoreComponents.html\"\n  }, \"core components\"), \". The diagram shows our records \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"after step 6\"), \", when the first order has shipped, and the index we create in step 7.\"), mdx(\"p\", null, mdx(\"img\", {\n    parentName: \"p\",\n    \"src\": \"/images/posts/dynamodb-tutorial-for-beginners/orders-data-model.svg\",\n    \"alt\": \"BeginnerOrders groups items by customerId and orders them by orderId; StatusIndex regroups those items by status so OPEN orders from C001 and C002 can be queried together.\"\n  })), mdx(\"h2\", null, \"1. Start DynamoDB Local and connect\"), mdx(\"p\", null, \"You need Docker running and either Node.js 22 or later with npm, or Python 3.10 or later with pip. AWS CLI v2 is optional, for the alternative table-creation command. The terminal commands below use Bash or Zsh; on Windows, use WSL with Docker integration.\"), mdx(\"p\", null, \"Start a disposable database on \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"port 8000\"), \":\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-bash\"\n  }, \"docker run -d --rm --name dynamodb-local -p 8000:8000 \\\\\\n  amazon/dynamodb-local:latest \\\\\\n  -jar DynamoDBLocal.jar -inMemory -sharedDb -disableTelemetry\\n\")), mdx(\"p\", null, \"Docker prints a container ID, which differs each time. \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"8000:8000\"), \" connects port 8000 on your computer to port 8000 inside the container. \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"-inMemory\"), \" discards the data when the container stops. \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"-sharedDb\"), \" lets both languages see the same database. For a persistent setup, use the \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"/run-dynamodb-locally/\"\n  }, \"DynamoDB Local setup guide\"), \".\"), mdx(\"p\", null, \"Create a working directory:\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-bash\"\n  }, \"mkdir dynamodb-beginner\\ncd dynamodb-beginner\\n\")), mdx(\"p\", null, \"Save each code block in the filename shown in its first comment, in this directory. Run scripts from this directory too. Each step's JavaScript and Python versions are alternatives, not commands to run one after the other.\"), mdx(\"h3\", null, \"JavaScript: install SDK v3 and connect\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-bash\"\n  }, \"npm init -y\\nnpm install @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb\\n\")), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-js\"\n  }, \"// client.mjs\\nimport { DynamoDBClient } from \\\"@aws-sdk/client-dynamodb\\\"\\nimport { DynamoDBDocumentClient } from \\\"@aws-sdk/lib-dynamodb\\\"\\n\\nexport const TABLE = \\\"BeginnerOrders\\\"\\nexport const raw = new DynamoDBClient({\\n  endpoint: \\\"http://localhost:8000\\\",\\n  region: \\\"us-east-1\\\",\\n  credentials: { accessKeyId: \\\"fake\\\", secretAccessKey: \\\"fake\\\" },\\n})\\nexport const db = DynamoDBDocumentClient.from(raw)\\n\")), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-js\"\n  }, \"// 01-connect.mjs\\nimport { ListTablesCommand } from \\\"@aws-sdk/client-dynamodb\\\"\\nimport { raw } from \\\"./client.mjs\\\"\\n\\nconst result = await raw.send(new ListTablesCommand({}))\\nconsole.log(`Tables: ${result.TableNames.length}`)\\n\")), mdx(\"p\", null, \"Run \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"node 01-connect.mjs\"), \". The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \".mjs\"), \" extension enables ES modules and top-level \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"await\"), \". The \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/programming-with-javascript.html\"\n  }, \"document client\"), \" accepts normal JavaScript values for item operations; \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"raw\"), \" handles table administration.\"), mdx(\"h3\", null, \"Python: install boto3 and connect\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-bash\"\n  }, \"python3 -m venv .venv\\nsource .venv/bin/activate\\npython -m pip install boto3\\n\")), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-python\"\n  }, \"# client.py\\nimport boto3\\n\\nTABLE = \\\"BeginnerOrders\\\"\\nddb = boto3.resource(\\n    \\\"dynamodb\\\",\\n    endpoint_url=\\\"http://localhost:8000\\\",\\n    region_name=\\\"us-east-1\\\",\\n    aws_access_key_id=\\\"fake\\\",\\n    aws_secret_access_key=\\\"fake\\\",\\n)\\nraw = ddb.meta.client\\ntable = ddb.Table(TABLE)\\n\")), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-python\"\n  }, \"# 01-connect.py\\nfrom client import raw\\n\\nresult = raw.list_tables()\\nprint(f\\\"Tables: {len(result['TableNames'])}\\\")\\n\")), mdx(\"p\", null, \"Run \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"python 01-connect.py\"), \". Keep the virtual environment active for subsequent Python commands. The \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://docs.aws.amazon.com/boto3/latest/guide/dynamodb.html\"\n  }, \"boto3 Table resource\"), \" accepts Python dictionaries for item operations.\"), mdx(\"p\", null, \"Output from either language on a fresh container:\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-text\"\n  }, \"Tables: 0\\n\")), mdx(\"p\", null, \"The dummy credentials are intentional. The local database needs a signed SDK request but does not authenticate an AWS account. If the connection fails immediately after starting Docker, allow the server to finish starting and rerun the connection script. AWS documents the local setup and credentials in its \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.DownloadingAndRunning.html\"\n  }, \"installation guide\"), \".\"), mdx(\"h2\", null, \"2. Create the orders table\"), mdx(\"p\", null, \"Use \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"one\"), \" of the three alternatives below. Each creates the same table. \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"HASH\"), \" means partition key, \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"RANGE\"), \" means sort key, and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"S\"), \" means String. \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AttributeDefinitions\"), \" declares only attributes used as keys, not \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"status\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"totalCents\"), \" or every possible item field.\"), mdx(\"h3\", null, \"JavaScript: CreateTable\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-js\"\n  }, \"// 02-create.mjs\\nimport { CreateTableCommand, waitUntilTableExists } from \\\"@aws-sdk/client-dynamodb\\\"\\nimport { raw, TABLE } from \\\"./client.mjs\\\"\\n\\nawait raw.send(new CreateTableCommand({\\n  TableName: TABLE,\\n  BillingMode: \\\"PAY_PER_REQUEST\\\",\\n  KeySchema: [\\n    { AttributeName: \\\"customerId\\\", KeyType: \\\"HASH\\\" },\\n    { AttributeName: \\\"orderId\\\", KeyType: \\\"RANGE\\\" },\\n  ],\\n  AttributeDefinitions: [\\n    { AttributeName: \\\"customerId\\\", AttributeType: \\\"S\\\" },\\n    { AttributeName: \\\"orderId\\\", AttributeType: \\\"S\\\" },\\n  ],\\n}))\\nawait waitUntilTableExists(\\n  { client: raw, maxWaitTime: 120, minDelay: 1 },\\n  { TableName: TABLE },\\n)\\nconsole.log(`${TABLE}: ACTIVE`)\\n\")), mdx(\"p\", null, \"Run \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"node 02-create.mjs\"), \".\"), mdx(\"h3\", null, \"Python: create_table\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-python\"\n  }, \"# 02-create.py\\nfrom client import raw, TABLE\\n\\nraw.create_table(\\n    TableName=TABLE,\\n    BillingMode=\\\"PAY_PER_REQUEST\\\",\\n    KeySchema=[\\n        {\\\"AttributeName\\\": \\\"customerId\\\", \\\"KeyType\\\": \\\"HASH\\\"},\\n        {\\\"AttributeName\\\": \\\"orderId\\\", \\\"KeyType\\\": \\\"RANGE\\\"},\\n    ],\\n    AttributeDefinitions=[\\n        {\\\"AttributeName\\\": \\\"customerId\\\", \\\"AttributeType\\\": \\\"S\\\"},\\n        {\\\"AttributeName\\\": \\\"orderId\\\", \\\"AttributeType\\\": \\\"S\\\"},\\n    ],\\n)\\nraw.get_waiter(\\\"table_exists\\\").wait(\\n    TableName=TABLE, WaiterConfig={\\\"Delay\\\": 1, \\\"MaxAttempts\\\": 120}\\n)\\nprint(f\\\"{TABLE}: ACTIVE\\\")\\n\")), mdx(\"p\", null, \"Run \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"python 02-create.py\"), \". Both versions wait for the table to become usable and print:\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-text\"\n  }, \"BeginnerOrders: ACTIVE\\n\")), mdx(\"h3\", null, \"Optional CLI alternative\"), mdx(\"p\", null, \"If you use this command, skip both SDK creation scripts. The first command prints the table name; the waiter finishes without output.\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-bash\"\n  }, \"AWS_ACCESS_KEY_ID=fake AWS_SECRET_ACCESS_KEY=fake AWS_SESSION_TOKEN= \\\\\\naws dynamodb create-table \\\\\\n  --endpoint-url http://localhost:8000 --region us-east-1 \\\\\\n  --table-name BeginnerOrders --billing-mode PAY_PER_REQUEST \\\\\\n  --attribute-definitions AttributeName=customerId,AttributeType=S AttributeName=orderId,AttributeType=S \\\\\\n  --key-schema AttributeName=customerId,KeyType=HASH AttributeName=orderId,KeyType=RANGE \\\\\\n  --query TableDescription.TableName --output text --no-cli-pager\\n\\nAWS_ACCESS_KEY_ID=fake AWS_SECRET_ACCESS_KEY=fake AWS_SESSION_TOKEN= \\\\\\naws dynamodb wait table-exists \\\\\\n  --endpoint-url http://localhost:8000 --region us-east-1 \\\\\\n  --table-name BeginnerOrders\\n\")), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-text\"\n  }, \"BeginnerOrders\\n\")), mdx(\"h2\", null, \"3. Write three orders with PutItem\"), mdx(\"p\", null, \"Save this shared fixture as \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"orders.json\"), \". Amounts are integer cents, so neither language needs a floating-point money value.\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-json\"\n  }, \"[\\n  { \\\"customerId\\\": \\\"C001\\\", \\\"orderId\\\": \\\"2026-10-01#1001\\\", \\\"status\\\": \\\"OPEN\\\", \\\"totalCents\\\": 2599 },\\n  { \\\"customerId\\\": \\\"C001\\\", \\\"orderId\\\": \\\"2026-10-03#1002\\\", \\\"status\\\": \\\"OPEN\\\", \\\"totalCents\\\": 4999 },\\n  { \\\"customerId\\\": \\\"C002\\\", \\\"orderId\\\": \\\"2026-10-02#1003\\\", \\\"status\\\": \\\"OPEN\\\", \\\"totalCents\\\": 1299 }\\n]\\n\")), mdx(\"h3\", null, \"JavaScript: PutCommand\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-js\"\n  }, \"// 03-put.mjs\\nimport { readFileSync } from \\\"node:fs\\\"\\nimport { PutCommand } from \\\"@aws-sdk/lib-dynamodb\\\"\\nimport { db, TABLE } from \\\"./client.mjs\\\"\\n\\nconst orders = JSON.parse(readFileSync(\\\"orders.json\\\", \\\"utf8\\\"))\\nfor (const order of orders) {\\n  await db.send(new PutCommand({ TableName: TABLE, Item: order }))\\n}\\nconsole.log(`Wrote ${orders.length} orders`)\\n\")), mdx(\"p\", null, \"Run \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"node 03-put.mjs\"), \".\"), mdx(\"h3\", null, \"Python: put_item\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-python\"\n  }, \"# 03-put.py\\nimport json\\nfrom client import table\\n\\nwith open(\\\"orders.json\\\", encoding=\\\"utf-8\\\") as source:\\n    orders = json.load(source)\\nfor order in orders:\\n    table.put_item(Item=order)\\nprint(f\\\"Wrote {len(orders)} orders\\\")\\n\")), mdx(\"p\", null, \"Run \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"python 03-put.py\"), \". Output from either:\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-text\"\n  }, \"Wrote 3 orders\\n\")), mdx(\"p\", null, mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PutCommand\"), \" calls the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"PutItem\"), \" API. An unconditional put replaces the entire existing item with the same primary key; it does not merge fields. Rerunning this seed step also resets a shipped order to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"OPEN\"), \". Step 8 adds overwrite protection. See the \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html\"\n  }, \"PutItem behavior\"), \".\"), mdx(\"h2\", null, \"4. Read one order with GetItem\"), mdx(\"p\", null, \"Provide \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"both\"), \" key attributes. \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ConsistentRead: true\"), \" requests the latest committed version from the base table, useful when checking a write. It is not the default.\"), mdx(\"h3\", null, \"JavaScript: GetCommand\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-js\"\n  }, \"// 04-get.mjs\\nimport { GetCommand } from \\\"@aws-sdk/lib-dynamodb\\\"\\nimport { db, TABLE } from \\\"./client.mjs\\\"\\n\\nconst { Item } = await db.send(new GetCommand({\\n  TableName: TABLE,\\n  Key: { customerId: \\\"C001\\\", orderId: \\\"2026-10-01#1001\\\" },\\n  ConsistentRead: true,\\n}))\\nif (!Item) throw new Error(\\\"Order not found\\\")\\nconsole.log(`${Item.customerId} | ${Item.orderId} | ${Item.status} | ${Item.totalCents}`)\\n\")), mdx(\"p\", null, \"Run \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"node 04-get.mjs\"), \".\"), mdx(\"h3\", null, \"Python: get_item\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-python\"\n  }, \"# 04-get.py\\nfrom client import table\\n\\nresult = table.get_item(\\n    Key={\\\"customerId\\\": \\\"C001\\\", \\\"orderId\\\": \\\"2026-10-01#1001\\\"},\\n    ConsistentRead=True,\\n)\\nitem = result.get(\\\"Item\\\")\\nif item is None:\\n    raise RuntimeError(\\\"Order not found\\\")\\nprint(f\\\"{item['customerId']} | {item['orderId']} | {item['status']} | {item['totalCents']}\\\")\\n\")), mdx(\"p\", null, \"Run \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"python 04-get.py\"), \". Output from either:\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-text\"\n  }, \"C001 | 2026-10-01#1001 | OPEN | 2599\\n\")), mdx(\"p\", null, \"These scripts print selected returned fields, not the whole SDK response with request metadata. A missing item produces no \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Item\"), \" field, which is why the scripts check it.\"), mdx(\"h2\", null, \"5. Query a customer's orders by sort-key prefix\"), mdx(\"p\", null, \"Retrieve \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"C001\"), \"'s October orders with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"customerId = :customer AND begins_with(orderId, :month)\"), \". The partition-key equality selects the customer; the sort-key condition selects the month. A \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.KeyConditionExpressions.html\"\n  }, \"key condition\"), \" cannot use arbitrary non-key attributes such as \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"status\"), \" on this table.\"), mdx(\"p\", null, \"Set \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Limit\"), \" to 1 deliberately to practice pagination with this tiny dataset. Each request evaluates at most one item here; keep passing \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"LastEvaluatedKey\"), \" back as \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ExclusiveStartKey\"), \" until it disappears.\"), mdx(\"h3\", null, \"JavaScript: QueryCommand\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-js\"\n  }, \"// 05-query.mjs\\nimport { QueryCommand } from \\\"@aws-sdk/lib-dynamodb\\\"\\nimport { db, TABLE } from \\\"./client.mjs\\\"\\n\\nlet cursor\\ndo {\\n  const page = await db.send(new QueryCommand({\\n    TableName: TABLE,\\n    KeyConditionExpression: \\\"customerId = :customer AND begins_with(orderId, :month)\\\",\\n    ExpressionAttributeValues: { \\\":customer\\\": \\\"C001\\\", \\\":month\\\": \\\"2026-10\\\" },\\n    ConsistentRead: true,\\n    Limit: 1,\\n    ExclusiveStartKey: cursor,\\n  }))\\n  for (const item of page.Items ?? []) {\\n    console.log(`${item.customerId} | ${item.orderId} | ${item.status}`)\\n  }\\n  cursor = page.LastEvaluatedKey\\n} while (cursor)\\n\")), mdx(\"p\", null, \"Run \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"node 05-query.mjs\"), \".\"), mdx(\"h3\", null, \"Python: query\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-python\"\n  }, \"# 05-query.py\\nfrom client import table\\n\\nparams = {\\n    \\\"KeyConditionExpression\\\": \\\"customerId = :customer AND begins_with(orderId, :month)\\\",\\n    \\\"ExpressionAttributeValues\\\": {\\\":customer\\\": \\\"C001\\\", \\\":month\\\": \\\"2026-10\\\"},\\n    \\\"ConsistentRead\\\": True,\\n    \\\"Limit\\\": 1,\\n}\\nwhile True:\\n    page = table.query(**params)\\n    for item in page.get(\\\"Items\\\", []):\\n        print(f\\\"{item['customerId']} | {item['orderId']} | {item['status']}\\\")\\n    if not page.get(\\\"LastEvaluatedKey\\\"):\\n        break\\n    params[\\\"ExclusiveStartKey\\\"] = page[\\\"LastEvaluatedKey\\\"]\\n\")), mdx(\"p\", null, \"Run \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"python 05-query.py\"), \". Output from either, in ascending sort-key order:\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-text\"\n  }, \"C001 | 2026-10-01#1001 | OPEN\\nC001 | 2026-10-03#1002 | OPEN\\n\")), mdx(\"p\", null, \"Remove the demonstration \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Limit\"), \" for normal use, but retain the loop. \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html\"\n  }, \"Query\"), \" evaluates at most 1 MB per page even without a limit. Filters run after evaluation and can return an empty page that still has a continuation key. Filtering does not reduce the read capacity already consumed. Set \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ScanIndexForward\"), \" to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"false\"), \" in JavaScript or \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"False\"), \" in Python when you want descending sort-key order.\"), mdx(\"h2\", null, \"6. Update an order's status\"), mdx(\"p\", null, \"Ship the first order with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SET #s = :shipped\"), \". \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"#s\"), \" aliases the attribute name \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"status\"), \", avoiding reserved-word conflicts, and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \":shipped\"), \" supplies the value. The existence condition prevents \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"UpdateItem\"), \" from creating a partial new order if you mistype its key.\"), mdx(\"h3\", null, \"JavaScript: UpdateCommand\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-js\"\n  }, \"// 06-update.mjs\\nimport { UpdateCommand } from \\\"@aws-sdk/lib-dynamodb\\\"\\nimport { db, TABLE } from \\\"./client.mjs\\\"\\n\\nconst { Attributes } = await db.send(new UpdateCommand({\\n  TableName: TABLE,\\n  Key: { customerId: \\\"C001\\\", orderId: \\\"2026-10-01#1001\\\" },\\n  UpdateExpression: \\\"SET #s = :shipped\\\",\\n  ConditionExpression: \\\"attribute_exists(customerId)\\\",\\n  ExpressionAttributeNames: { \\\"#s\\\": \\\"status\\\" },\\n  ExpressionAttributeValues: { \\\":shipped\\\": \\\"SHIPPED\\\" },\\n  ReturnValues: \\\"ALL_NEW\\\",\\n}))\\nconsole.log(`${Attributes.orderId} | ${Attributes.status} | ${Attributes.totalCents}`)\\n\")), mdx(\"p\", null, \"Run \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"node 06-update.mjs\"), \".\"), mdx(\"h3\", null, \"Python: update_item\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-python\"\n  }, \"# 06-update.py\\nfrom client import table\\n\\nresult = table.update_item(\\n    Key={\\\"customerId\\\": \\\"C001\\\", \\\"orderId\\\": \\\"2026-10-01#1001\\\"},\\n    UpdateExpression=\\\"SET #s = :shipped\\\",\\n    ConditionExpression=\\\"attribute_exists(customerId)\\\",\\n    ExpressionAttributeNames={\\\"#s\\\": \\\"status\\\"},\\n    ExpressionAttributeValues={\\\":shipped\\\": \\\"SHIPPED\\\"},\\n    ReturnValues=\\\"ALL_NEW\\\",\\n)\\nitem = result[\\\"Attributes\\\"]\\nprint(f\\\"{item['orderId']} | {item['status']} | {item['totalCents']}\\\")\\n\")), mdx(\"p\", null, \"Run \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"python 06-update.py\"), \". Output from either:\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-text\"\n  }, \"2026-10-01#1001 | SHIPPED | 2599\\n\")), mdx(\"p\", null, \"The amount remains unchanged. \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html\"\n  }, \"UpdateItem\"), \" changes the specified attributes and can return their new values in the same response. See \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"/dynamodb-update-expression/\"\n  }, \"DynamoDB update expressions\"), \" for counters, nested paths and removing attributes.\"), mdx(\"h2\", null, \"7. Add a GSI and query open orders\"), mdx(\"p\", null, \"The table's key cannot answer \\u201Call open orders across customers\\u201D directly. Add \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"StatusIndex\"), \" with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"status\"), \" as its partition key and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"orderId\"), \" as its sort key. \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ProjectionType: ALL\"), \" copies all item attributes into the index so the query can return the customer and amount too.\"), mdx(\"h3\", null, \"JavaScript: create the index\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-js\"\n  }, \"// 07-add-index.mjs\\nimport { UpdateTableCommand } from \\\"@aws-sdk/client-dynamodb\\\"\\nimport { raw, TABLE } from \\\"./client.mjs\\\"\\n\\nawait raw.send(new UpdateTableCommand({\\n  TableName: TABLE,\\n  AttributeDefinitions: [\\n    { AttributeName: \\\"status\\\", AttributeType: \\\"S\\\" },\\n    { AttributeName: \\\"orderId\\\", AttributeType: \\\"S\\\" },\\n  ],\\n  GlobalSecondaryIndexUpdates: [{ Create: {\\n    IndexName: \\\"StatusIndex\\\",\\n    KeySchema: [\\n      { AttributeName: \\\"status\\\", KeyType: \\\"HASH\\\" },\\n      { AttributeName: \\\"orderId\\\", KeyType: \\\"RANGE\\\" },\\n    ],\\n    Projection: { ProjectionType: \\\"ALL\\\" },\\n  } }],\\n}))\\nconsole.log(\\\"StatusIndex creation requested\\\")\\n\")), mdx(\"p\", null, \"Run \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"node 07-add-index.mjs\"), \" \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"once\"), \", then save and run \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"node 07-query-index.mjs\"), \":\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-js\"\n  }, \"// 07-query-index.mjs\\nimport { setTimeout as sleep } from \\\"node:timers/promises\\\"\\nimport { DescribeTableCommand } from \\\"@aws-sdk/client-dynamodb\\\"\\nimport { QueryCommand } from \\\"@aws-sdk/lib-dynamodb\\\"\\nimport { raw, db, TABLE } from \\\"./client.mjs\\\"\\n\\nlet active = false\\nfor (let attempt = 0; attempt < 120; attempt++) {\\n  const { Table } = await raw.send(new DescribeTableCommand({ TableName: TABLE }))\\n  active = Table.GlobalSecondaryIndexes?.some(\\n    (index) => index.IndexName === \\\"StatusIndex\\\" && index.IndexStatus === \\\"ACTIVE\\\",\\n  ) ?? false\\n  if (active) break\\n  await sleep(1000)\\n}\\nif (!active) throw new Error(\\\"Index not ready; rerun only 07-query-index.mjs later\\\")\\nconsole.log(\\\"StatusIndex: ACTIVE\\\")\\n\\nlet cursor\\ndo {\\n  const page = await db.send(new QueryCommand({\\n    TableName: TABLE,\\n    IndexName: \\\"StatusIndex\\\",\\n    KeyConditionExpression: \\\"#s = :open\\\",\\n    ExpressionAttributeNames: { \\\"#s\\\": \\\"status\\\" },\\n    ExpressionAttributeValues: { \\\":open\\\": \\\"OPEN\\\" },\\n    ExclusiveStartKey: cursor,\\n  }))\\n  for (const item of page.Items ?? []) {\\n    console.log(`${item.customerId} | ${item.orderId} | ${item.status}`)\\n  }\\n  cursor = page.LastEvaluatedKey\\n} while (cursor)\\n\")), mdx(\"h3\", null, \"Python: create and query the index\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-python\"\n  }, \"# 07-add-index.py\\nfrom client import raw, TABLE\\n\\nraw.update_table(\\n    TableName=TABLE,\\n    AttributeDefinitions=[\\n        {\\\"AttributeName\\\": \\\"status\\\", \\\"AttributeType\\\": \\\"S\\\"},\\n        {\\\"AttributeName\\\": \\\"orderId\\\", \\\"AttributeType\\\": \\\"S\\\"},\\n    ],\\n    GlobalSecondaryIndexUpdates=[{\\\"Create\\\": {\\n        \\\"IndexName\\\": \\\"StatusIndex\\\",\\n        \\\"KeySchema\\\": [\\n            {\\\"AttributeName\\\": \\\"status\\\", \\\"KeyType\\\": \\\"HASH\\\"},\\n            {\\\"AttributeName\\\": \\\"orderId\\\", \\\"KeyType\\\": \\\"RANGE\\\"},\\n        ],\\n        \\\"Projection\\\": {\\\"ProjectionType\\\": \\\"ALL\\\"},\\n    }}],\\n)\\nprint(\\\"StatusIndex creation requested\\\")\\n\")), mdx(\"p\", null, \"Run \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"python 07-add-index.py\"), \" \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"once\"), \", then save and run \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"python 07-query-index.py\"), \":\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-python\"\n  }, \"# 07-query-index.py\\nimport time\\nfrom client import raw, table, TABLE\\n\\nfor attempt in range(120):\\n    description = raw.describe_table(TableName=TABLE)[\\\"Table\\\"]\\n    active = any(\\n        index[\\\"IndexName\\\"] == \\\"StatusIndex\\\" and index[\\\"IndexStatus\\\"] == \\\"ACTIVE\\\"\\n        for index in description.get(\\\"GlobalSecondaryIndexes\\\", [])\\n    )\\n    if active:\\n        break\\n    time.sleep(1)\\nelse:\\n    raise RuntimeError(\\\"Index not ready; rerun only 07-query-index.py later\\\")\\nprint(\\\"StatusIndex: ACTIVE\\\")\\n\\nparams = {\\n    \\\"IndexName\\\": \\\"StatusIndex\\\",\\n    \\\"KeyConditionExpression\\\": \\\"#s = :open\\\",\\n    \\\"ExpressionAttributeNames\\\": {\\\"#s\\\": \\\"status\\\"},\\n    \\\"ExpressionAttributeValues\\\": {\\\":open\\\": \\\"OPEN\\\"},\\n}\\nwhile True:\\n    page = table.query(**params)\\n    for item in page.get(\\\"Items\\\", []):\\n        print(f\\\"{item['customerId']} | {item['orderId']} | {item['status']}\\\")\\n    if not page.get(\\\"LastEvaluatedKey\\\"):\\n        break\\n    params[\\\"ExclusiveStartKey\\\"] = page[\\\"LastEvaluatedKey\\\"]\\n\")), mdx(\"p\", null, \"Combined output from the two scripts, in either language:\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-text\"\n  }, \"StatusIndex creation requested\\nStatusIndex: ACTIVE\\nC002 | 2026-10-02#1003 | OPEN\\nC001 | 2026-10-03#1002 | OPEN\\n\")), mdx(\"p\", null, \"The shipped order is absent; the open orders span both customers. Existing items are backfilled when the index is created. The query script checks the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"index's\"), \" \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"IndexStatus\"), \", not just the table's status, because an \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.OnlineOps.html\"\n  }, \"index under construction\"), \" cannot yet be queried. A cloud backfill may take longer than this local waiting loop; rerun the query script later rather than requesting creation again.\"), mdx(\"p\", null, mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html\"\n  }, \"GSI reads are eventually consistent\"), \": do not add \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ConsistentRead: true\"), \". Recently changed records can take time to appear in the index in AWS, even when the index is already active. Local results do not establish production replication timing.\"), mdx(\"p\", null, \"This low-volume example uses only a few status values. At higher traffic, putting every open order under the same index partition-key value can concentrate writes. Review the \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"/dynamodb-gsi/\"\n  }, \"GSI design guide\"), \" before using this exact index for a busy fulfillment system. Index storage and index updates also add AWS cost.\"), mdx(\"h3\", null, \"Optional: see the table in a GUI\"), mdx(\"p\", null, \"Connect \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"/dynamodb-local-admin-gui/\"\n  }, \"Dynobase to DynamoDB Local\"), \" using endpoint \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"http://localhost:8000\"), \", region \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"us-east-1\"), \" and the dummy credentials \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"fake\"), \"/\", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"fake\"), \". Open \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"BeginnerOrders\"), \" to inspect the items before cleanup. For the index query, choose \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"StatusIndex\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"status = OPEN\"), \".\"), mdx(\"p\", null, mdx(\"img\", {\n    parentName: \"p\",\n    \"src\": \"/images/features-3/data.jpg\",\n    \"alt\": \"Dynobase's data view shows order items in columns for keys, customer, status and total.\"\n  })), mdx(\"p\", null, \"This existing product screenshot shows a different example Orders table; your tutorial table has \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"customerId\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"orderId\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"status\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"totalCents\"), \".\"), mdx(\"h2\", null, \"8. Prevent overwrites with a conditional write\"), mdx(\"p\", null, \"Try inserting a conflicting order with a total of one cent. \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"attribute_not_exists(customerId)\"), \" is evaluated against the item identified by the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"complete primary key\"), \", so it prevents replacing that order while still allowing other orders for \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"C001\"), \". The condition and write are atomic; checking existence with a separate read would leave a race.\"), mdx(\"h3\", null, \"JavaScript: conditional PutCommand\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-js\"\n  }, \"// 08-conditional.mjs\\nimport { PutCommand, GetCommand } from \\\"@aws-sdk/lib-dynamodb\\\"\\nimport { db, TABLE } from \\\"./client.mjs\\\"\\n\\nconst key = { customerId: \\\"C001\\\", orderId: \\\"2026-10-01#1001\\\" }\\ntry {\\n  await db.send(new PutCommand({\\n    TableName: TABLE,\\n    Item: { ...key, status: \\\"OPEN\\\", totalCents: 1 },\\n    ConditionExpression: \\\"attribute_not_exists(customerId)\\\",\\n  }))\\n  throw new Error(\\\"Expected the duplicate to be rejected\\\")\\n} catch (error) {\\n  if (error.name !== \\\"ConditionalCheckFailedException\\\") throw error\\n  console.log(\\\"Duplicate rejected: ConditionalCheckFailedException\\\")\\n}\\nconst { Item } = await db.send(new GetCommand({\\n  TableName: TABLE, Key: key, ConsistentRead: true,\\n}))\\nconsole.log(`Preserved: ${Item.status} | ${Item.totalCents}`)\\n\")), mdx(\"p\", null, \"Run \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"node 08-conditional.mjs\"), \".\"), mdx(\"h3\", null, \"Python: conditional put_item\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-python\"\n  }, \"# 08-conditional.py\\nfrom botocore.exceptions import ClientError\\nfrom client import table\\n\\nkey = {\\\"customerId\\\": \\\"C001\\\", \\\"orderId\\\": \\\"2026-10-01#1001\\\"}\\ntry:\\n    table.put_item(\\n        Item={**key, \\\"status\\\": \\\"OPEN\\\", \\\"totalCents\\\": 1},\\n        ConditionExpression=\\\"attribute_not_exists(customerId)\\\",\\n    )\\n    raise RuntimeError(\\\"Expected the duplicate to be rejected\\\")\\nexcept ClientError as error:\\n    if error.response[\\\"Error\\\"][\\\"Code\\\"] != \\\"ConditionalCheckFailedException\\\":\\n        raise\\n    print(\\\"Duplicate rejected: ConditionalCheckFailedException\\\")\\nitem = table.get_item(Key=key, ConsistentRead=True)[\\\"Item\\\"]\\nprint(f\\\"Preserved: {item['status']} | {item['totalCents']}\\\")\\n\")), mdx(\"p\", null, \"Run \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"python 08-conditional.py\"), \". Output from either:\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-text\"\n  }, \"Duplicate rejected: ConditionalCheckFailedException\\nPreserved: SHIPPED | 2599\\n\")), mdx(\"p\", null, \"The error is the expected success case for this test. Only that error is handled; connection failures and other problems still stop the script. In an application, report a duplicate or conflict to the caller. \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"/dynamodb-conditional-write-and-update/\"\n  }, \"Conditional writes and updates\"), \" also support rules such as \\u201Cship only if the current status is OPEN.\\u201D\"), mdx(\"h2\", null, \"9. Delete an order and clean up\"), mdx(\"p\", null, \"Delete \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"C002\"), \"'s order, confirm it is gone with a consistent read, and delete the whole tutorial table. Deleting the table also removes its GSI and remaining items.\"), mdx(\"h3\", null, \"JavaScript: DeleteCommand and DeleteTable\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-js\"\n  }, \"// 09-cleanup.mjs\\nimport { DeleteCommand, GetCommand } from \\\"@aws-sdk/lib-dynamodb\\\"\\nimport { DeleteTableCommand, waitUntilTableNotExists } from \\\"@aws-sdk/client-dynamodb\\\"\\nimport { db, raw, TABLE } from \\\"./client.mjs\\\"\\n\\nconst key = { customerId: \\\"C002\\\", orderId: \\\"2026-10-02#1003\\\" }\\nawait db.send(new DeleteCommand({ TableName: TABLE, Key: key }))\\nconst result = await db.send(new GetCommand({\\n  TableName: TABLE, Key: key, ConsistentRead: true,\\n}))\\nconsole.log(`Order exists: ${Boolean(result.Item)}`)\\nawait raw.send(new DeleteTableCommand({ TableName: TABLE }))\\nawait waitUntilTableNotExists(\\n  { client: raw, maxWaitTime: 120, minDelay: 1 },\\n  { TableName: TABLE },\\n)\\nconsole.log(\\\"BeginnerOrders deleted\\\")\\n\")), mdx(\"p\", null, \"Run \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"node 09-cleanup.mjs\"), \".\"), mdx(\"h3\", null, \"Python: delete_item and delete_table\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-python\"\n  }, \"# 09-cleanup.py\\nfrom client import table, raw, TABLE\\n\\nkey = {\\\"customerId\\\": \\\"C002\\\", \\\"orderId\\\": \\\"2026-10-02#1003\\\"}\\ntable.delete_item(Key=key)\\nresult = table.get_item(Key=key, ConsistentRead=True)\\nprint(f\\\"Order exists: {str('Item' in result).lower()}\\\")\\nraw.delete_table(TableName=TABLE)\\nraw.get_waiter(\\\"table_not_exists\\\").wait(\\n    TableName=TABLE, WaiterConfig={\\\"Delay\\\": 1, \\\"MaxAttempts\\\": 120}\\n)\\nprint(\\\"BeginnerOrders deleted\\\")\\n\")), mdx(\"p\", null, \"Run \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"python 09-cleanup.py\"), \". Output from either:\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-text\"\n  }, \"Order exists: false\\nBeginnerOrders deleted\\n\")), mdx(\"p\", null, \"If you want to try the other language, keep Docker running and return to step 2 after installing that language's dependencies and saving its client file. Otherwise stop the disposable container:\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-bash\"\n  }, \"docker stop dynamodb-local\\n\")), mdx(\"p\", null, \"Output:\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-text\"\n  }, \"dynamodb-local\\n\")), mdx(\"h2\", null, \"10. Move from DynamoDB Local to AWS\"), mdx(\"p\", null, \"The local exercise is complete. This optional step requires an AWS account and can incur charges. The local examples above were executed against DynamoDB Local; the AWS configuration below was checked without creating cloud resources.\"), mdx(\"h3\", null, \"Choose on-demand and understand the free tier\"), mdx(\"p\", null, \"Keep \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"BillingMode: \\\"PAY_PER_REQUEST\\\"\"), \" for a first cloud experiment with unpredictable traffic. The \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"/dynamodb-free-tier/\"\n  }, \"DynamoDB free tier\"), \" includes \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"25 GB of Standard-class storage and 25 provisioned read capacity units plus 25 provisioned write capacity units\"), \", per Region and payer account. The capacity allowance \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"does not cover on-demand read/write requests\"), \". The Standard storage allowance does apply to on-demand tables. Check \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://aws.amazon.com/dynamodb/pricing/\"\n  }, \"AWS pricing\"), \" for your Region and include index writes and storage in your estimate.\"), mdx(\"p\", null, \"For eligible new accounts created on or after July 15, 2025, AWS offers $100 in sign-up credits and up to $100 more through qualifying activities. The Free account plan ends after six months or when its credits run out, whichever comes first; its account closes unless upgraded. These credits are separate from DynamoDB's ongoing allowances. See the current \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/free-tier-plans.html\"\n  }, \"AWS account plan rules\"), \".\"), mdx(\"h3\", null, \"Configure credentials and IAM\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Use an AWS IAM Identity Center profile for development, or an IAM role when running on AWS. Do not replace \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"fake\"), \" with a long-lived key pasted into source code.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Give the application's identity \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"dynamodb:GetItem\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"PutItem\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"UpdateItem\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"DeleteItem\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Query\"), \" on the tutorial table ARN. Also allow \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"dynamodb:Query\"), \" on its \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"index/StatusIndex\"), \" ARN. The \", mdx(\"a\", {\n    parentName: \"li\",\n    \"href\": \"https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/iam-policy-example-data-crud.html\"\n  }, \"AWS table-access policy example\"), \" shows the resource structure.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"The identity running the setup and cleanup scripts additionally needs \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"CreateTable\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"DescribeTable\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"UpdateTable\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"DeleteTable\"), \" for the table. Only the connection-check script needs \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ListTables\"), \". Keep these administrative permissions separate from the application's runtime role.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Clear any dummy credential environment variables, select your authenticated development profile, and clear local endpoint overrides such as \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"AWS_ENDPOINT_URL\"), \" or \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"AWS_ENDPOINT_URL_DYNAMODB\"), \". Both SDKs can then use their default credential chains. Confirm the account and Region before running a write or cleanup script.\")), mdx(\"p\", null, \"DynamoDB Local cannot validate your cloud IAM permissions. Test those in the intended AWS development account.\"), mdx(\"h3\", null, \"JavaScript: replace client.mjs\"), mdx(\"p\", null, \"Replace the entire local client file with this version. The SDK selects the AWS endpoint and obtains credentials from the \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-credentials-node.html\"\n  }, \"Node.js credential provider chain\"), \".\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-js\"\n  }, \"// client.mjs (AWS replacement)\\nimport { DynamoDBClient } from \\\"@aws-sdk/client-dynamodb\\\"\\nimport { DynamoDBDocumentClient } from \\\"@aws-sdk/lib-dynamodb\\\"\\n\\nexport const TABLE = \\\"BeginnerOrders\\\"\\nexport const raw = new DynamoDBClient({ region: \\\"us-east-1\\\" })\\nexport const db = DynamoDBDocumentClient.from(raw)\\n\")), mdx(\"h3\", null, \"Python: replace client.py\"), mdx(\"p\", null, mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://docs.aws.amazon.com/boto3/latest/guide/credentials.html\"\n  }, \"Boto3's credential chain\"), \" supplies the authenticated profile or role:\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-python\"\n  }, \"# client.py (AWS replacement)\\nimport boto3\\n\\nTABLE = \\\"BeginnerOrders\\\"\\nddb = boto3.resource(\\\"dynamodb\\\", region_name=\\\"us-east-1\\\")\\nraw = ddb.meta.client\\ntable = ddb.Table(TABLE)\\n\")), mdx(\"p\", null, \"Run steps 2-8 again in your chosen language to create the cloud table and seed the three example orders. The AWS CLI alternative would also need its local endpoint and dummy credential assignments removed; changing the SDK client file does not change CLI commands. Switching endpoints does \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"not\"), \" upload local data. For this small exercise, rerunning the fixture is the migration.\"), mdx(\"p\", null, \"Cloud table creation and index backfill are asynchronous. If the step 2 waiter times out, check the existing table's status before continuing; do not blindly recreate it. For step 7, rerun only the query script while the index finishes. When finished experimenting, run step 9 with the AWS client to remove the cloud table and index too. Stopping Docker affects only the local database.\"), mdx(\"h2\", null, \"Common beginner mistakes\"), mdx(\"table\", null, mdx(\"thead\", {\n    parentName: \"table\"\n  }, mdx(\"tr\", {\n    parentName: \"thead\"\n  }, mdx(\"th\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Symptom\"), mdx(\"th\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Cause and fix\"))), mdx(\"tbody\", {\n    parentName: \"table\"\n  }, mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Connection refused or endpoint connection error\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Start Docker, wait a few seconds, and verify the client uses \", mdx(\"inlineCode\", {\n    parentName: \"td\"\n  }, \"http://localhost:8000\"), \" over plain HTTP, not HTTPS.\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, mdx(\"inlineCode\", {\n    parentName: \"td\"\n  }, \"ResourceInUseException\"), \" during creation\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"The table already exists. Run only one creation alternative per clean database.\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, mdx(\"inlineCode\", {\n    parentName: \"td\"\n  }, \"ResourceNotFoundException\")), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Check the table name and endpoint. In AWS, also check the account, Region and table/index readiness.\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"\\u201CThe provided key element does not match the schema\\u201D\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, mdx(\"inlineCode\", {\n    parentName: \"td\"\n  }, \"GetItem\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"td\"\n  }, \"UpdateItem\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"td\"\n  }, \"DeleteItem\"), \" require both String keys for this table.\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Query cannot use \", mdx(\"inlineCode\", {\n    parentName: \"td\"\n  }, \"status\")), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Query \", mdx(\"inlineCode\", {\n    parentName: \"td\"\n  }, \"StatusIndex\"), \" instead of the base table. The key condition must match the chosen table or index.\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Python rejects a float\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Boto3 uses \", mdx(\"inlineCode\", {\n    parentName: \"td\"\n  }, \"Decimal\"), \" for non-integer numbers. This fixture uses integer cents.\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"A duplicate put changes data\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"An unconditional put replaces the entire item. Add the condition from step 8.\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Local data disappears after stopping Docker\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"This tutorial deliberately uses \", mdx(\"inlineCode\", {\n    parentName: \"td\"\n  }, \"-inMemory\"), \"; restart and seed again, or use persistent local storage.\")))), mdx(\"p\", null, \"Local testing proves that these requests work against the local API. It does not prove AWS throttling, billing, IAM enforcement or propagation timing. AWS lists the \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.UsageNotes.html\"\n  }, \"differences from the managed service\"), \".\"), mdx(\"h2\", null, \"What should you learn next?\"), mdx(\"p\", null, \"Use this as the first lab in a DynamoDB course, then extend the same project:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Read \", mdx(\"a\", {\n    parentName: \"li\",\n    \"href\": \"/dynamodb-keys/\"\n  }, \"DynamoDB keys\"), \" and \", mdx(\"a\", {\n    parentName: \"li\",\n    \"href\": \"/dynamodb-data-types/\"\n  }, \"data types\"), \". Add a second order on the same date without overwriting the first.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Compare \", mdx(\"a\", {\n    parentName: \"li\",\n    \"href\": \"/dynamodb-scan-vs-query/\"\n  }, \"Scan and Query\"), \". Explain why a status filter on a full-table scan is different from querying \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"StatusIndex\"), \".\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Work through \", mdx(\"a\", {\n    parentName: \"li\",\n    \"href\": \"/dynamodb-single-table-design-guide/\"\n  }, \"single-table design\"), \". Add customer profile items and decide which access patterns need an index before writing code.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Apply the \", mdx(\"a\", {\n    parentName: \"li\",\n    \"href\": \"/dynamodb-best-practices/\"\n  }, \"DynamoDB best practices\"), \" to key distribution, item sizes and operational checks.\")), mdx(\"p\", null, \"Keep the \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"/dynamodb-nodejs/\"\n  }, \"Node.js examples\"), \" or \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"/dynamodb-python-with-boto3/\"\n  }, \"boto3 examples\"), \" nearby as a reference. For additional projects, browse \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"/dynamodb-tutorials/\"\n  }, \"more tutorials\"), \".\"), mdx(FAQ, {\n    questions: [{\n      question: \"Can I learn DynamoDB without an AWS account?\",\n      answer: \"Yes. Run DynamoDB Local with Docker, point your SDK at the local endpoint, and use dummy credentials. This tutorial's table, item operations, GSI and conditional writes all run locally; the optional AWS transition needs an account.\"\n    }, {\n      question: \"How long does this DynamoDB tutorial take?\",\n      answer: \"Allow 30-45 minutes using either the JavaScript or Python track, with Docker and your language runtime already installed. You build an orders table, query an index, protect a write and clean up. Installing prerequisites or working through both languages takes longer.\"\n    }, {\n      question: \"Is DynamoDB suitable for beginners?\",\n      answer: \"You can start with a small table and a few SDK calls. The main design task is deciding which questions the application must answer, then choosing keys and indexes for those queries. This tutorial uses customer orders to make those choices visible.\"\n    }, {\n      question: \"Is an AWS DynamoDB tutorial free to run?\",\n      answer: \"The DynamoDB Local portion creates no AWS resources or AWS usage charges. In AWS, Standard storage has a free allowance, but the free provisioned capacity allowance does not cover on-demand read/write requests. Eligible account credits may cover charges.\"\n    }, {\n      question: \"Why use Query instead of Scan when getting started?\",\n      answer: \"Query selects items by a partition-key value and an optional sort-key condition. Scan examines table or index data before applying filters. Design a key or index for a recurring application lookup instead of making a scan its default implementation.\"\n    }, {\n      question: \"Does finishing this tutorial replace a DynamoDB course?\",\n      answer: \"It covers a first hands-on lab, including keys, CRUD, pagination, a GSI and conditions. Continue with single-table design, capacity planning and operational best practices before using the same patterns for a production workload.\"\n    }],\n    mdxType: \"FAQ\"\n  }));\n}\n;\nMDXContent.isMDXComponent = true;","frontmatter":{"publishedDate":"2026-10-23","updatedDate":"2026-10-23","path":"/dynamodb-tutorial-for-beginners/","metaTitle":"DynamoDB Tutorial for Beginners: Build an Orders Table","metaDescription":"Build a DynamoDB orders table in 30-45 minutes using Node.js or Python. Run locally, query keys and a GSI, prevent overwrites, and prepare for AWS.","title":"DynamoDB tutorial for beginners: build an orders table","authorName":"Rafal Wilinski","authorLink":"https://x.com/rafalwilinski","authorImage":"/images/authors/rafal-wilinski.jpg","image":"/images/posts/dynamodb-tutorial-for-beginners/social.png"}}},"pageContext":{}},
    "staticQueryHashes": ["3649515864"]}