Dynamodb
Build with Amazon DynamoDB as a serverless NoSQL database. Use this skill when the project needs to integrate DynamoDB for key-value and document storage, single-table design, global tables, streams, TTL, or serverless-first data access. Covers the AWS SDK v3, single-table design, GSIs, queries, scans, transactions, and DynamoDB Streams.
You are a database specialist who integrates DynamoDB into projects. DynamoDB is a fully managed, serverless NoSQL database from AWS that provides single-digit millisecond performance at any scale with automatic scaling and zero maintenance. ## Key Points - Design for access patterns first — list every query before creating the table - Use single-table design — one table with PK/SK patterns for all entities - Use GSIs to support additional query patterns - Use `begins_with` on sort keys for hierarchical queries - Use conditional writes to prevent race conditions - Use TTL for auto-expiring data (sessions, logs, temporary records) - Use DynamoDB Streams for event-driven architectures - Designing tables like relational databases — one table per entity - Using Scan instead of Query — scans read the entire table - Not planning GSIs upfront — retrofitting access patterns is expensive - Using FilterExpression instead of proper key design — filters run after reads - Storing large items (>400KB) — use S3 for large objects ## Quick Example ```bash npm install @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb ```
skilldb get database-services-skills/dynamodbFull skill: 243 linesAmazon DynamoDB Integration
You are a database specialist who integrates DynamoDB into projects. DynamoDB is a fully managed, serverless NoSQL database from AWS that provides single-digit millisecond performance at any scale with automatic scaling and zero maintenance.
Core Philosophy
Single-table design
DynamoDB works best when you put all your data in one table with carefully designed partition and sort keys. This isn't like relational design — you model your data around your access patterns, not your entities.
Access patterns first
Before creating a table, list every query your application needs. Design your keys and indexes to serve those queries. You cannot query DynamoDB flexibly — if you didn't plan for a query, you can't do it without a full table scan.
No joins, no aggregations
DynamoDB doesn't support joins or server-side aggregations. If you need them, denormalize your data or compute aggregates in your application. This is the trade-off for unlimited scale.
Setup
Install
npm install @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
Initialize
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
const client = new DynamoDBClient({ region: process.env.AWS_REGION });
const docClient = DynamoDBDocumentClient.from(client, {
marshallOptions: { removeUndefinedValues: true },
});
Key Techniques
Single-table design
// All entities in one table with PK/SK patterns
// PK | SK | Attributes
// USER#alice | PROFILE | name, email, plan
// USER#alice | POST#2024-01-15 | title, content, status
// USER#alice | APIKEY#key123 | key, active, usageCount
// POST#uuid | METADATA | title, authorId, status
// POST#uuid | COMMENT#timestamp | text, authorId
const TABLE = process.env.TABLE_NAME!;
CRUD operations
import { GetCommand, PutCommand, UpdateCommand, DeleteCommand, QueryCommand } from '@aws-sdk/lib-dynamodb';
// Put (create or overwrite)
await docClient.send(new PutCommand({
TableName: TABLE,
Item: {
PK: `USER#${userId}`,
SK: 'PROFILE',
name: 'Alice',
email: 'alice@example.com',
plan: 'free',
createdAt: new Date().toISOString(),
GSI1PK: 'USERS',
GSI1SK: `USER#${userId}`,
},
}));
// Get (exact key lookup)
const { Item } = await docClient.send(new GetCommand({
TableName: TABLE,
Key: { PK: `USER#${userId}`, SK: 'PROFILE' },
}));
// Query (partition key + sort key condition)
const { Items } = await docClient.send(new QueryCommand({
TableName: TABLE,
KeyConditionExpression: 'PK = :pk AND begins_with(SK, :sk)',
ExpressionAttributeValues: {
':pk': `USER#${userId}`,
':sk': 'POST#',
},
ScanIndexForward: false, // Descending order
Limit: 20,
}));
// Query with filter
const { Items: activePosts } = await docClient.send(new QueryCommand({
TableName: TABLE,
KeyConditionExpression: 'PK = :pk AND begins_with(SK, :sk)',
FilterExpression: '#status = :status',
ExpressionAttributeNames: { '#status': 'status' },
ExpressionAttributeValues: {
':pk': `USER#${userId}`,
':sk': 'POST#',
':status': 'published',
},
}));
// Update
await docClient.send(new UpdateCommand({
TableName: TABLE,
Key: { PK: `USER#${userId}`, SK: 'PROFILE' },
UpdateExpression: 'SET #name = :name, updatedAt = :now',
ExpressionAttributeNames: { '#name': 'name' },
ExpressionAttributeValues: {
':name': 'Alice Updated',
':now': new Date().toISOString(),
},
}));
// Conditional update
await docClient.send(new UpdateCommand({
TableName: TABLE,
Key: { PK: `USER#${userId}`, SK: 'PROFILE' },
UpdateExpression: 'SET plan = :newPlan',
ConditionExpression: 'plan = :currentPlan',
ExpressionAttributeValues: {
':newPlan': 'pro',
':currentPlan': 'free',
},
}));
// Delete
await docClient.send(new DeleteCommand({
TableName: TABLE,
Key: { PK: `USER#${userId}`, SK: 'PROFILE' },
}));
// Atomic counter
await docClient.send(new UpdateCommand({
TableName: TABLE,
Key: { PK: `POST#${postId}`, SK: 'METADATA' },
UpdateExpression: 'ADD viewCount :inc',
ExpressionAttributeValues: { ':inc': 1 },
}));
Global Secondary Index (GSI) queries
// Query GSI — e.g., find all users
const { Items } = await docClient.send(new QueryCommand({
TableName: TABLE,
IndexName: 'GSI1',
KeyConditionExpression: 'GSI1PK = :pk',
ExpressionAttributeValues: { ':pk': 'USERS' },
}));
// Query by email (GSI2 with email as key)
const { Items: byEmail } = await docClient.send(new QueryCommand({
TableName: TABLE,
IndexName: 'GSI2',
KeyConditionExpression: 'GSI2PK = :email',
ExpressionAttributeValues: { ':email': `EMAIL#${email}` },
}));
Transactions
import { TransactWriteCommand } from '@aws-sdk/lib-dynamodb';
await docClient.send(new TransactWriteCommand({
TransactItems: [
{
Update: {
TableName: TABLE,
Key: { PK: `POST#${postId}`, SK: 'METADATA' },
UpdateExpression: 'SET #status = :status',
ExpressionAttributeNames: { '#status': 'status' },
ExpressionAttributeValues: { ':status': 'published' },
},
},
{
Put: {
TableName: TABLE,
Item: {
PK: `USER#${authorId}`,
SK: `NOTIFICATION#${Date.now()}`,
type: 'post_published',
postId,
},
},
},
],
}));
Pagination
async function* paginate(params: QueryCommandInput) {
let lastKey: Record<string, any> | undefined;
do {
const { Items, LastEvaluatedKey } = await docClient.send(
new QueryCommand({ ...params, ExclusiveStartKey: lastKey })
);
yield Items ?? [];
lastKey = LastEvaluatedKey;
} while (lastKey);
}
for await (const page of paginate({ TableName: TABLE, KeyConditionExpression: 'PK = :pk', ExpressionAttributeValues: { ':pk': 'USERS' }, IndexName: 'GSI1' })) {
for (const item of page) {
console.log(item);
}
}
Best Practices
- Design for access patterns first — list every query before creating the table
- Use single-table design — one table with PK/SK patterns for all entities
- Use GSIs to support additional query patterns
- Use
begins_withon sort keys for hierarchical queries - Use conditional writes to prevent race conditions
- Use TTL for auto-expiring data (sessions, logs, temporary records)
- Use DynamoDB Streams for event-driven architectures
Anti-Patterns
- Designing tables like relational databases — one table per entity
- Using Scan instead of Query — scans read the entire table
- Not planning GSIs upfront — retrofitting access patterns is expensive
- Using FilterExpression instead of proper key design — filters run after reads
- Storing large items (>400KB) — use S3 for large objects
- Hot partition keys — distribute writes across partitions
- Not handling pagination — DynamoDB returns max 1MB per query
Install this skill directly: skilldb add database-services-skills
Related Skills
Fauna
Build with Fauna as a distributed document-relational database. Use this skill when the project needs to integrate Fauna for globally distributed data, ACID transactions, document-relational modeling, FQL queries, or serverless database access. Covers the Fauna v10 client, FQL, schema, indexes, and authentication.
Firebase Firestore
Build with Firebase Firestore as a NoSQL document database. Use this skill when the project needs to integrate Firestore for real-time document storage, offline persistence, security rules, queries, aggregations, or Cloud Functions triggers. Covers the Firestore SDK (v9+), data modeling, queries, security rules, real-time listeners, batched writes, transactions, and Cloud Functions integration.
Mongodb
Build with MongoDB as a document database. Use this skill when the project needs to integrate MongoDB or Mongoose for document storage, flexible schemas, aggregation pipelines, Atlas Search, change streams, or transactions. Covers the MongoDB Node.js driver, Mongoose ODM, schema design, indexing, aggregation, and Atlas features.
Neo4j
Build with Neo4j for graph data and relationship-heavy queries. Use this skill when the project needs a graph database for social networks, recommendation engines, knowledge graphs, fraud detection, or any domain where relationships between entities are central. Covers Cypher query language, the Neo4j JavaScript driver, graph modeling, and common graph patterns.
Neon
Build with Neon as a serverless PostgreSQL database. Use this skill when the project needs to integrate Neon for serverless Postgres, branching, autoscaling, connection pooling, or edge-compatible database access. Covers the Neon serverless driver, branching workflows, connection pooling, and integration with ORMs.
Planetscale
Build with PlanetScale as a serverless MySQL database. Use this skill when the project needs to integrate PlanetScale for serverless MySQL, branching, non-blocking schema changes, connection pooling, or Vitess-powered horizontal scaling. Covers the PlanetScale serverless driver, branching, deploy requests, and ORM integration.