Skip to content
VibORM
Esc
navigateopen⌘Jpreview
On this page

Migrate

File-based migrations with version control for production — each migration stored as a SQL file committable to git

When to Use Migrate

  • Production deployments - Versioned, repeatable schema changes
  • Team collaboration - Migration files in version control
  • CI/CD pipelines - Automated migration deployment
  • Rollback support - Down migrations for reverting changes

Workflow Overview

1. Make changes to your VibORM models
2. Generate a migration file
3. Review the generated SQL
4. Apply the migration to database

Generate Migrations

Generate a new migration by comparing your current models against the previous snapshot:

# Generate a migration with auto-generated name
npx viborm migrate generate

# Generate with a custom name
npx viborm migrate generate --name add-users-table

# Preview without creating files
npx viborm migrate generate --dry-run
import { createMigrationClient } from "viborm/migrations";
import { createFsStorageDriver } from "viborm/migrations/storage/fs";
import { client } from "./client";

const migrations = createMigrationClient(client, {
  storageDriver: createFsStorageDriver("./migrations"),
});

// Generate with custom name
const result = await migrations.generate({
  name: "add-users-table",
});

if (result.entry) {
  console.log(result.message); // e.g. "Generated migration 0003_add-users-table"
  console.log(`Operations: ${result.operations.length}`);
  console.log(`Files written: ${result.written}`);
} else {
  console.log("No changes detected");
}

// Preview without creating files
const preview = await migrations.preview();

Generate Options

Option CLI API Description
Name --name <name> name: string Custom migration name
Output directory --out <dir> dir: string Output directory (default: ./migrations)
Dry run --dry-run dryRun: boolean Preview without creating files
Resolver - resolver: Resolver Handle ambiguous changes
Enum resolver - enumValueResolver: EnumValueResolver Handle enum value removals

Check Migration Status

View which migrations have been applied:

# Show migration status
npx viborm migrate status

Output:

Migration Status:
  [x] 0000_initial.sql (applied 2024-01-15)
  [x] 0001_add-users.sql (applied 2024-01-16)
  [ ] 0002_add-posts.sql (pending)
const statuses = await migrations.status();

for (const status of statuses) {
  const state = status.appliedAt ? "applied" : "pending";
  const date = status.appliedAt?.toLocaleDateString() ?? "";
  console.log(`${status.entry.name}: ${state} ${date}`);
}

// Get only pending migrations
const pending = await migrations.pending();
console.log(`${pending.length} migrations pending`);

Status Options

Option CLI API Description
Directory --dir <dir> - Migrations directory (default: ./migrations)
Table name --table-name <name> tableName: string Migrations tracking table name

Apply Migrations

Apply pending migrations to the database:

# Apply all pending migrations
npx viborm migrate apply

# Apply up to a specific migration index
npx viborm migrate apply --to 5

# Preview without applying
npx viborm migrate apply --dry-run
// Apply all pending
const result = await migrations.apply();
console.log(`Applied ${result.applied.length} migrations`);

// Apply up to a specific migration index
await migrations.apply({ to: 5 });

// Preview without applying
await migrations.apply({ dryRun: true });

Apply Options

Option CLI API Description
To --to <index> to: number Apply up to this migration index
Directory --dir <dir> - Migrations directory (default: ./migrations)
Table name --table-name <name> tableName: string Migrations tracking table name
Dry run --dry-run dryRun: boolean Preview without applying
Force --force - Skip confirmation prompts

Roll Back Migrations

Every generated migration includes a down migration (stored in meta/_down/) that reverses its operations. Lossy operations (dropped tables or columns) are annotated with warnings in the down file — the structure is restored, but not the data.

# Roll back the last migration
npx viborm migrate down

# Roll back the last 3 migrations
npx viborm migrate down --steps 3

# Roll back to a specific migration (by name or index)
npx viborm migrate down --to add-users

# Preview without executing
npx viborm migrate down --dry-run
// Roll back the last migration
const result = await migrations.down({ steps: 1 });
console.log(`Rolled back: ${result.rolledBack.length} migrations`);

// Roll back to a specific point
await migrations.down({ to: "add-users" });

// Preview
await migrations.down({ steps: 1, dryRun: true });

Down Options

Option CLI API Description
Steps --steps <n> steps: number Number of migrations to roll back
To --to <migration> to: string | number Roll back to this migration (exclusive)
Directory --dir <dir> - Migrations directory (default: ./migrations)
Table name --table-name <name> tableName: string Migrations tracking table name
Dry run --dry-run dryRun: boolean Preview without executing
Force --force - Skip confirmation prompts

Drop Migration Tracking

migrate drop removes the last N migrations (default: 1) from the tracking table. It does not revert any database changes — to undo schema changes, use migrate down instead:

# Remove the last migration from tracking
npx viborm migrate drop

# Remove the last 3 migrations from tracking
npx viborm migrate drop --count 3
// Removes from tracking only (doesn't touch tables or data)
await migrations.rollback({ count: 1 });

Drop Options

Option CLI API Description
Last --last - Drop only the last migration (the default)
Count --count <n> count: number Drop the last N migrations
Directory --dir <dir> - Migrations directory (default: ./migrations)
Table name --table-name <name> tableName: string Migrations tracking table name
Force --force - Skip confirmation prompts

Reset Database (API only)

reset() drops all tables and enums, clears the migration tracking table, and re-applies every migration from the journal. There is no CLI equivalent:

await migrations.reset();

Squash Migrations

Combine multiple migrations into one:

// List migrations to review
const entries = await migrations.list();
console.log(entries.map(e => `${e.idx}: ${e.name}`));

// Squash development migrations
const result = await migrations.squash({
  from: 0,
  to: 5,
  name: "initial-schema",
  cleanup: true, // Delete original files
});

console.log(`Squashed into: ${result.newEntry.name}`);

Squash Options

Option Type Description
from number Starting migration index
to number Ending migration index (inclusive)
name string Name for the squashed migration
cleanup boolean Delete original migration files
dryRun boolean Preview without squashing

Migration File Structure

migrations/
├── 0000_initial.sql
├── 0001_add-users.sql
├── 0002_add-posts.sql
└── meta/
    ├── _journal.json
    ├── _snapshot.json
    └── _down/
        ├── 0000_initial.sql
        ├── 0001_add-users.sql
        └── 0002_add-posts.sql

Up Migration

The main SQL file contains the forward migration:

CREATE TABLE "users" (
  "id" serial PRIMARY KEY,
  "email" text NOT NULL,
  "created_at" timestamp DEFAULT NOW()
);

CREATE UNIQUE INDEX "users_email_idx" ON "users" ("email");

Down Migration

The down file contains the rollback SQL:

DROP INDEX "users_email_idx";
DROP TABLE "users";

Journal

Tracks migration metadata:

{
  "version": "1",
  "dialect": "postgresql",
  "entries": [
    {
      "idx": 0,
      "version": "20240115120000",
      "name": "initial",
      "when": 1705320000000,
      "checksum": "sha256:abc123..."
    }
  ]
}

Snapshot

Stores the schema state after migrations:

{
  "tables": [...],
  "enums": [...]
}

Reading Migration Data

Access migration data programmatically:

// List all migrations
const entries = await migrations.list();

// Get full journal
const journal = await migrations.journal();

// Get schema snapshot
const snapshot = await migrations.snapshot();

// Read migration SQL content
const sql = await migrations.read(entries[0]);

Error Handling

import { MigrationError, isMigrationError } from "viborm/migrations";

try {
  await migrations.apply();
} catch (error) {
  if (isMigrationError(error)) {
    console.error(`Migration error: ${error.code}`);
    console.error(error.message);
  }
}
Error Code Description
MIGRATION_STORAGE_REQUIRED Operation requires a storage driver but none was provided
MIGRATION_CHECKSUM_MISMATCH Migration file was modified after being applied
MIGRATION_NOT_FOUND Referenced migration doesn’t exist
MIGRATION_ALREADY_APPLIED Migration was already applied

CI/CD Integration

Example GitHub Actions workflow:

- name: Apply migrations
  run: npx viborm migrate apply
  env:
    DATABASE_URL: ${{ secrets.DATABASE_URL }}

For programmatic deployments:

import { createMigrationClient } from "viborm/migrations";
import { createFsStorageDriver } from "viborm/migrations/storage/fs";
import { client } from "./db/client";

const migrations = createMigrationClient(client, {
  storageDriver: createFsStorageDriver("./migrations"),
});

const pending = await migrations.pending();
if (pending.length > 0) {
  console.log(`Applying ${pending.length} migrations...`);
  await migrations.apply();
  console.log("Done!");
}

Next Steps

Was this page helpful?