Skip to main content
Rollback migrations to restore the database to a previous state.

Rollback Commands

rollback

Rollback recent batchMultiple at once

rollback --all

Rollback all migrationsReturn to initial state

down

One at a timeStep-by-step rollback

status

Check status before rollbackVerify batches

Basic Rollback

Rollback Recent Batch

pnpm sonamu migrate rollback
Execution Result:
Batch 2 rolled back: 1 migration
✅ 20251220143200_alter_users_add1.ts
migrate rollback rolls back all migrations in the most recent batch.

Check Current Status

pnpm sonamu migrate status
Output:
Executed migrations (Batch 1):
  ✅ 20251220143022_create__users.ts
  ✅ 20251220143100_create__posts.ts

Executed migrations (Batch 2):
  ✅ 20251220143200_alter_users_add1.ts  ← This batch gets rolled back

Pending migrations:
  ⏳ 20251220143300_alter_posts_add1.ts

Rollback Methods

Batch-based Rollback

# Check status
pnpm sonamu migrate status

# Batch 3 (most recent)
 20251220150000_alter_users_add2.ts
 20251220150100_alter_posts_add2.ts

# Batch 2
 20251220143200_alter_users_add1.ts

# Rollback (Batch 3 only)
pnpm sonamu migrate rollback

# Result: 2 migrations in Batch 3 rolled back
Batch 3 rolled back: 2 migrations
 20251220150000_alter_users_add2.ts
 20251220150100_alter_posts_add2.ts

One at a Time

pnpm sonamu migrate down
First Run:
Batch 2 rolled back: 1 migration
✅ 20251220143200_alter_users_add1.ts
Second Run:
Batch 1 rolled back: 1 migration
✅ 20251220143100_create__posts.ts

Full Rollback

pnpm sonamu migrate rollback --all
Execution Result:
Batch 2 rolled back: 1 migration
✅ 20251220143200_alter_users_add1.ts

Batch 1 rolled back: 2 migrations
✅ 20251220143100_create__posts.ts
✅ 20251220143022_create__users.ts

All migrations rolled back successfully
rollback --all deletes all tables! Never use in production.

The down Function

Basic Structure

export async function down(knex: Knex): Promise<void> {
  // Perform reverse operation of up
}

CREATE TABLE Rollback

// up: Create table
export async function up(knex: Knex): Promise<void> {
  await knex.schema.createTable("users", (table) => {
    table.increments().primary();
    table.string("email", 255).notNullable();
  });
}

// down: Drop table
export async function down(knex: Knex): Promise<void> {
  return knex.schema.dropTable("users");
}

ALTER TABLE Rollback

// up: Add column
export async function up(knex: Knex): Promise<void> {
  await knex.schema.alterTable("users", (table) => {
    table.string("phone", 20).nullable();
  });
}

// down: Drop column
export async function down(knex: Knex): Promise<void> {
  await knex.schema.alterTable("users", (table) => {
    table.dropColumns("phone");
  });
}

FOREIGN KEY Rollback

// up: Add foreign key
export async function up(knex: Knex): Promise<void> {
  return knex.schema.alterTable("employees", (table) => {
    table
      .foreign("user_id")
      .references("users.id")
      .onUpdate("CASCADE")
      .onDelete("CASCADE");
  });
}

// down: Drop foreign key
export async function down(knex: Knex): Promise<void> {
  return knex.schema.alterTable("employees", (table) => {
    table.dropForeign(["user_id"]);
  });
}

Rollback Scenarios

Scenario 1: Fix Incorrect Migration

# 1. Run migration
pnpm sonamu migrate run
 20251220143200_alter_users_add1.ts

# 2. Discover problem (wrong column type)

# 3. Rollback
pnpm sonamu migrate rollback

# 4. Fix migration file
vim src/migrations/20251220143200_alter_users_add1.ts

# 5. Run again
pnpm sonamu migrate run

Scenario 2: Production Emergency Rollback

# 1. Problem discovered after production deployment

# 2. Immediate rollback
NODE_ENV=production pnpm sonamu migrate rollback

# 3. Deploy previous application version

# 4. Fix problem and redeploy

Scenario 3: Partial Rollback

# Current status
Batch 3: alter_users_add2.ts, alter_posts_add2.ts
Batch 2: alter_users_add1.ts
Batch 1: create__users.ts, create__posts.ts

# Rollback only Batch 3 (keep Batch 2, 1)
pnpm sonamu migrate rollback

# Result
Batch 2: alter_users_add1.ts Retained
Batch 1: create__users.ts, create__posts.ts Retained

Data Preservation

Data Loss on Rollback

// up: Add column
export async function up(knex: Knex): Promise<void> {
  await knex.schema.alterTable("users", (table) => {
    table.string("phone", 20).nullable();
  });
}

// down: Drop column (data loss!)
export async function down(knex: Knex): Promise<void> {
  await knex.schema.alterTable("users", (table) => {
    table.dropColumns("phone");  // ← All phone data deleted
  });
}
Data loss warning!Data cannot be recovered when columns/tables are dropped. Backup is required in production!

Safe Rollback Pattern

// Step 1: Add column (nullable)
export async function up(knex: Knex): Promise<void> {
  await knex.schema.alterTable("users", (table) => {
    table.string("new_phone", 20).nullable();
  });
}

// Step 2: Data migration
await knex("users").update({
  new_phone: knex.raw("old_phone"),
});

// Step 3: Keep old_phone column (rollback preparation)
// → Delete later in separate migration

Non-reversible Cases

Data Transformation

// up: Type change
export async function up(knex: Knex): Promise<void> {
  await knex.raw(
    `ALTER TABLE users 
     ALTER COLUMN age TYPE integer 
     USING age::integer`
  );
}

// down: Original data cannot be restored
export async function down(knex: Knex): Promise<void> {
  await knex.raw(
    `ALTER TABLE users 
     ALTER COLUMN age TYPE varchar(255)`
  );
  // "25" → 25 → "25" (loss if original was "025")
}

Data Deletion

// up: Delete specific data
export async function up(knex: Knex): Promise<void> {
  await knex("users")
    .where("is_deleted", true)
    .delete();
}

// down: Deleted data cannot be restored
export async function down(knex: Knex): Promise<void> {
  // Cannot restore!
}

Rollback Strategies

1. Backup First

# PostgreSQL backup
pg_dump -U postgres -d mydb > backup_before_rollback.sql

# Rollback
pnpm sonamu migrate rollback

# Restore if issues occur
psql -U postgres -d mydb < backup_before_rollback.sql

2. Staging Test

# Test rollback in staging
NODE_ENV=staging pnpm sonamu migrate rollback

# If no issues, production
NODE_ENV=production pnpm sonamu migrate rollback

3. Incremental Rollback

# Rollback one at a time
pnpm sonamu migrate down

# Check status
pnpm sonamu migrate status

# Test application

# Next rollback
pnpm sonamu migrate down

Error Handling

Rollback Failure

pnpm sonamu migrate rollback
Error:
❌ Error rolling back 20251220143200_alter_users_add1.ts
   Error: column "phone" does not exist
Causes:
  • Migration was only partially applied
  • down function doesn’t match up
  • DB was modified manually
Solutions:
  1. Manually verify DB state
  2. Modify knex_migrations table
  3. Fix down function and retry

Forced Rollback

-- Remove from knex_migrations
DELETE FROM knex_migrations 
WHERE name = '20251220143200_alter_users_add1.ts';

-- Manually undo changes
ALTER TABLE users DROP COLUMN phone;

Rollback Logs

Verbose Logs

pnpm sonamu migrate rollback --verbose
Output:
Rolling back migration: 20251220143200_alter_users_add1.ts
SQL: ALTER TABLE "users" DROP COLUMN "phone"
✅ Completed in 23ms

Batch 2 rolled back: 1 migration

Querying Rollback History

-- Check recently rolled back migrations
SELECT * FROM knex_migrations 
WHERE batch = (SELECT MAX(batch) + 1 FROM knex_migrations);

Practical Tips

1. Verify down Function

// Test: up → down → up
test("migration reversibility", async () => {
  await migration.up(knex);
  await migration.down(knex);
  await migration.up(knex);  // Should be runnable again
});

2. Document Rollback Plan

# Rollback Plan

## Migration: 20251220143200_alter_users_add1.ts

### Changes
- Add column: users.phone

### Rollback Impact
- Data loss: users.phone data deleted
- Estimated time: 5 seconds
- Downtime: None (nullable column)

### Rollback Steps
1. `pnpm sonamu migrate rollback`
2. Deploy previous app version
3. Verify phone field not used

3. Auto-rollback Script

#!/bin/bash
# auto-rollback.sh

echo "Creating backup..."
pg_dump -U postgres -d mydb > backup.sql

echo "Rolling back..."
pnpm sonamu migrate rollback

if [ $? -eq 0 ]; then
  echo "✅ Rollback successful"
else
  echo "❌ Rollback failed, restoring backup..."
  psql -U postgres -d mydb < backup.sql
fi

Next Steps