Skip to main content

Migrations

DynamoDB migration tool for single table design with schema versioning.

Why Migrations?​

In traditional SQL databases, schema changes require ALTER TABLE statements. DynamoDB is schemaless, but your application still has an implicit schema - the structure of your data.

When you need to:

  • Add new fields to existing items
  • Transform data formats
  • Change key structures
  • Add new entity types

You need a way to evolve your data safely and consistently across environments.

Features​

  • Single Table Design - Built specifically for DynamoDB single table design patterns
  • Semantic Versioning - Migrations use semver (0.1.0, 0.2.0, 1.0.0) for clear version tracking
  • Up/Down Migrations - Support for both applying and rolling back migrations
  • Migration History - All migration records stored in your DynamoDB table
  • Distributed Locking - Prevents concurrent migrations with automatic lock expiration
  • Dry Run Mode - Preview changes before applying them
  • TypeScript First - Full TypeScript support with type safety

How It Works​

All migration tracking happens within your DynamoDB table using Single Table Design principles:

PK SK version status appliedAt
-----------------------------------------------------------------------
_SCHEMA#VERSION 0.1.0 0.1.0 applied 2025-03-29T10:00:00Z
_SCHEMA#VERSION 0.2.0 0.2.0 applied 2025-03-29T11:00:00Z
_SCHEMA#VERSION#CURRENT _SCHEMA#VERSION 0.2.0 - 2025-03-29T11:00:00Z

No separate tracking table needed - everything lives in your existing table.

Quick Example​

import { Migration } from '@ftschopp/dynatable-migrations';

export const migration: Migration = {
version: '0.1.0',
name: 'add_user_email',
description: 'Add email field to all users',

async up(context) {
const { client, tableName, dynamodb } = context;
const { ScanCommand, UpdateCommand } = dynamodb;

// Find all users
const result = await client.send(
new ScanCommand({
TableName: tableName,
FilterExpression: 'begins_with(PK, :pk)',
ExpressionAttributeValues: { ':pk': 'USER#' },
})
);

// Add email field
for (const user of result.Items || []) {
await client.send(
new UpdateCommand({
TableName: tableName,
Key: { PK: user.PK, SK: user.SK },
UpdateExpression: 'SET email = :email',
ExpressionAttributeValues: { ':email': null },
})
);
}
},

async down(context) {
// Rollback logic...
},
};

Next Steps​