dynobase-icon
Dynobase

DynamoDB Rust: AWS SDK Query and CRUD Cheat Sheet

Chris Biscardi

Written by Chris Biscardi

Last updated: October 23rd, 2026

Writing DynamoDB code by hand? Dynobase turns supported data operations into application code or scripts, and generates CDK, CloudFormation or Terraform for an existing table. Code generation in Dynobase

Use aws-sdk-dynamodb with aws-config and Tokio to call DynamoDB from Rust. Configure the client with BehaviorVersion::latest(), build requests with the fluent API, and use into_paginator() for complete Query and Scan results. The SDK exposes typed AttributeValue maps; optional serde_dynamo conversion lets application code use Rust structs.

Table of contents

Setup

Create a project and add these dependencies to Cargo.toml. The versions checked for this refresh are aws-sdk-dynamodb 1.134.0 and aws-config 1.13.0; both declare Rust 1.94.1 as their minimum supported Rust version. Keep your application's Cargo.lock so later compatible releases do not silently change a deployment.

The AWS SDK crate reference documents its types and operations. aws-config supplies the credential and Region provider chains. Use AWS_PROFILE and AWS_REGION locally, or the workload's IAM role on AWS; never embed real credentials.

Put this first block in src/main.rs, then add the remaining Rust functions below it. The shared imports cover the whole collection. The entry point constructs a client without making writes; call only the operation you want to test. Run cargo check to check the combined example.

Every example uses my-table with String partition key name and Number sort key createdAt. Dates are numeric YYYYMMDD examples. GSI1 uses String pokemon_type and Number createdAt. This lets the same sample items support both individual lookups and type-based queries.

The optional serde_dynamo project is unarchived and its published 4.3.0 release supports AWS SDK 1.x through the aws-sdk-dynamodb+1 feature. Its latest published release was September 2025, so treat it as a third-party dependency rather than an AWS support commitment. The integration below is compatible with the SDK versions shown here; low-level examples work without a serializer.

Create table

Define only the attributes used as table or index keys. Required-field builders such as AttributeDefinition and KeySchemaElement return a Result; propagate build errors with ?. Creation is asynchronous, so use the SDK waiter instead of assuming a successful CreateTable response means the table is active.

The waiter requires the client::Waiters trait import. Its duration is an application wait budget, not an AWS completion guarantee. A failed wait does not necessarily mean table creation failed; inspect the table state before retrying creation.

Delete table

Deleting a table also deletes its items and indexes. Wait for absence when a test needs to reuse the table name. Do not call this function during normal startup.

List tables

List names in the configured account and Region with a paginator. A single ListTables call is only one page. The paginator's stream yields a result for every page; propagate failures rather than silently returning a partial list.

Get all items / Scan

A Scan evaluates the table's items. Consume pages as they arrive to keep memory bounded. Here the output accessor returns an empty slice when no items were returned, so there is no need to unwrap an optional collection.

For a filter, set the expression and its aliases before converting the request into a paginator:

The optional lengthInSeconds Number is not in the basic sample struct. Items missing it will not match this filter. Filtering occurs after reads, so it reduces returned data, not the read capacity already consumed. A filtered page may be empty while the paginator still has more work.

Get item

A single-item read needs both primary-key fields. A missing item yields None, not a missing-table error. consistent_read(true) requests a strongly consistent base-table read.

The typed version uses serde_dynamo::from_item. Required struct fields must exist with compatible types; #[serde(default)] allows score to be absent. Do not convert high-precision DynamoDB decimals blindly to f64; choose a representation that preserves your application's precision.

Batch get item

BatchGet retrieves known keys, including from multiple tables, with up to 100 items and a 16 MB response. Keys must be distinct. The results are unordered and missing items are omitted.

A successful response can contain UnprocessedKeys. This loop accumulates completed reads and retries only the remaining keys. The bounded retry count and delay are example policy choices; use a job-level deadline in production.

Add another table-name entry to the request map for multiple tables, then collect each table's response separately. A durable worker should return or persist partial results and pending keys when its retry budget expires; this small example reports failure instead of claiming completion.

Put item

A put creates or replaces the entire item at its complete key. The low-level representation stores numbers in AttributeValue::N as decimal strings. These are DynamoDB Numbers, not String attributes.

The second function serializes the same schema with serde_dynamo::to_item. #[serde(rename = "createdAt")] maps the idiomatic Rust field name to the existing DynamoDB attribute. Use condition_expression("attribute_not_exists(#name)") with a #name alias if an existing item must not be overwritten.

Batch write / put item

BatchWrite supports up to 25 puts/deletes and a 16 MB request. It does not support partial updates or conditions, and the overall batch is not atomic. A successful response can contain UnprocessedItems; those operations have not yet succeeded.

To include a delete, construct a DeleteRequest with the complete key and use WriteRequest::builder().delete_request(...). Do not put and delete the same key in one batch. Application retries should preserve the outstanding subset so they do not overwrite newer changes with an already completed put.

Query for a set of items

A Query selects equality on the partition key and optionally a sort-key condition. This reads snapshots of bulbasaur after a numeric date. The aliases and placeholder values match exactly. Use a paginator because one call is not guaranteed to return all matches.

A filter can further restrict non-key attributes, but is applied after the key-matched items have been read. The Query vs. Scan guide explains when to model another key-based access pattern rather than scanning.

Query an index

Use the index name and its keys. GSI1 finds all grass-type records by date, regardless of their base-table partition keys. A GSI supports eventual consistency only; do not request strong consistency. This index projects all attributes, so typed decoding has the fields it needs.

For an LSI, supply its index name and its sort-key condition while keeping the table's partition key. A GSI query cannot fetch attributes that were not projected into the index.

Update item

An update changes only selected attributes and can create the item when it is absent. The following example updates a non-key nickname. It cannot rename the primary-key attribute's value in place. Request AllNew to return the resulting item.

Conditionally update item

A conditional update checks the current item atomically with the mutation. This changes the nickname only when the item is a grass type and has not been soft-deleted. It fails instead of creating a partial item when the record is absent.

Handle a condition failure as a conflict or a business-rule outcome. Repeating the identical condition without changing state usually will not help. Error handling is shown below.

Increment an item attribute

Use an atomic update instead of a read-modify-write cycle. if_not_exists initializes a missing counter to zero. An atomic increment is not idempotent: retrying an uncertain application request can increment twice, so use an idempotency design for business-critical counts.

Delete item

Delete by the same complete key used for GetItem. An absent item is not an error. Add a condition if deletion depends on current state; ReturnValue::AllOld can return the removed attributes.

Delete all items

DynamoDB has no table-wide delete operation. Scan the primary keys and delete each item, or group them into batch deletes with unprocessed-item retries. This deliberately sequential example bounds concurrency and projects only the two key attributes. Stop concurrent writers when the goal is an empty table; a scan is not a snapshot.

Simple transaction

Transactions make multiple writes succeed or fail together. This cart example uses the same table schema: seed a stock item at stock#bulbasaur / 0 with numeric count, then atomically decrement it and insert a distinct reservation at cart#123#bulbasaur / 20261023. The condition prevents negative stock and a duplicate reservation.

Give each logical reservation a unique token and reuse it only for identical retries. The idempotency window lasts 10 minutes after completion. Transactions accept up to 100 unique items and 4 MB in one account and Region, with one action per item. MRSC global tables do not support transactions. Stock and cart records do not have the fields needed for Pokemon decoding, so scan them as raw maps or define separate Rust types.

Read transaction

A read transaction fetches specified keys atomically. It can span tables in the same account and Region, and its response order matches request order. It is not a transactional table scan.

Query with sorting

Query orders results by the queried sort key. Numbers sort numerically, strings by UTF-8 bytes. Set scan_index_forward(false) for descending order, such as newest snapshots first. There is no arbitrary server-side sort field for Scan.

Query and Scan pagination

Both Query and Scan evaluate up to 1 MB before filtering. A limit also counts evaluated items, not matching results. The into_paginator() examples continue automatically, including after empty filtered pages.

For a user-facing page, return the last_evaluated_key from one request and pass the complete map as exclusive_start_key on the next. Preserve every returned key component, including index attributes for a GSI cursor. Pagination details explain why item count is not a reliable completion signal.

When implementing a manual loop, stop when the key map is absent or empty, not merely when items is absent. Bind an externally supplied cursor to the same authorized query and key conditions.

Handle SdkError and service errors

SDK operations return SdkError<OperationError>, which can represent a service response, a timeout, a connection failure or a request-construction failure. Inspect the service error before treating a failure as a condition conflict. into_service_error() consumes the wrapper; for a non-service failure it produces the operation's unhandled error, so retain the original wrapper when transport details matter.

Use the operation-specific error enum, such as PutItemError, rather than the retired preview SDK's ErrorKind structs. The SDK handles configured request retries, but your application must handle final errors and successful batch responses containing unprocessed work. AWS's Rust DynamoDB examples provide further operation-specific examples.

Frequently Asked Questions

Which crates do I need for DynamoDB in Rust?

Use aws-sdk-dynamodb, aws-config and an async runtime such as Tokio. Optional serde_dynamo converts Serde structs to and from DynamoDB attribute maps.

How do I paginate a DynamoDB query in Rust?

Build the query, call into_paginator().send(), then consume each page with next().await. Propagate errors and continue through empty filtered pages.

Does Rust have a DynamoDB DocumentClient?

The AWS client exposes typed AttributeValue maps. The third-party serde_dynamo crate adds struct serialization with its aws-sdk-dynamodb+1 feature; it does not replace the AWS client.

Why does build need a question mark in current Rust SDK examples?

Builders with required fields can return Result. Propagate that build error with ? before passing the completed value into a request; some other builders return their value directly.

How do I handle a conditional-write failure in Rust?

Inspect the operation's service error inside SdkError and check for ConditionalCheckFailedException. Keep timeouts and connection failures separate from a condition that did not match.

Dynobase is a Professional GUI Client for DynamoDB

Start your 7-day free trial today

Product Features

Download
/
Pricing
/
Member Portal
/
Privacy
/
EULA
/
Twitter
© 2026 Dynobase