Skip to main content

Best Database Migration Tools for Node.js in 2026

·PkgPulse Team

TL;DR

Drizzle Kit for TypeScript-first SQL migrations; Prisma Migrate for Prisma users; Flyway for polyglot teams. Drizzle Kit (~1M weekly downloads) generates SQL migrations from your Drizzle schema — you see the SQL, you control it. Prisma Migrate (~5M downloads, bundled with Prisma) is automatic migration generation with a shadow database. Flyway (~400K downloads) is language-agnostic, versioned SQL files, the most explicit.

Key Takeaways

  • Prisma Migrate: ~5M downloads — automatic, shadow DB, TypeScript schema
  • Drizzle Kit: ~1M downloads — SQL-first, you see the SQL, TypeScript schema
  • Flyway: ~400K downloads — polyglot, SQL files versioned with V1_2_3 naming
  • Drizzle Kitdrizzle-kit generate creates SQL; drizzle-kit push applies (dev)
  • All tools — support up migrations; rollbacks vary (Prisma via preview, Drizzle manual)

Drizzle Kit (TypeScript-First)

// Drizzle ORM schema — migrations generated from this
// db/schema.ts
import { pgTable, serial, varchar, integer, timestamp, boolean, index } from 'drizzle-orm/pg-core';
import { relations } from 'drizzle-orm';

export const users = pgTable('users', {
  id: serial('id').primaryKey(),
  email: varchar('email', { length: 255 }).notNull().unique(),
  name: varchar('name', { length: 100 }).notNull(),
  role: varchar('role', { length: 20 }).notNull().default('user'),
  createdAt: timestamp('created_at').notNull().defaultNow(),
  deletedAt: timestamp('deleted_at'),
}, (table) => ({
  emailIdx: index('email_idx').on(table.email),
}));

export const posts = pgTable('posts', {
  id: serial('id').primaryKey(),
  title: varchar('title', { length: 255 }).notNull(),
  content: varchar('content', { length: 10000 }),
  published: boolean('published').notNull().default(false),
  authorId: integer('author_id').notNull().references(() => users.id),
  createdAt: timestamp('created_at').notNull().defaultNow(),
});

export const usersRelations = relations(users, ({ many }) => ({
  posts: many(posts),
}));
// drizzle.config.ts
import type { Config } from 'drizzle-kit';

export default {
  schema: './db/schema.ts',
  out: './db/migrations',        // Where SQL migration files go
  dialect: 'postgresql',
  dbCredentials: {
    url: process.env.DATABASE_URL!,
  },
  verbose: true,
  strict: true,                   // Fail on destructive changes
} satisfies Config;
# Drizzle Kit commands

# Generate SQL migration from schema changes
drizzle-kit generate
# Creates: db/migrations/0001_add_posts_table.sql
# You can review/edit this SQL before applying!

# Push to database (dev — no migration file, direct apply)
drizzle-kit push

# Apply pending migrations (production)
drizzle-kit migrate

# Inspect existing database schema
drizzle-kit introspect

# Open Drizzle Studio (visual DB browser)
drizzle-kit studio
-- db/migrations/0001_add_posts_table.sql (auto-generated, reviewable)
CREATE TABLE IF NOT EXISTS "posts" (
  "id" serial PRIMARY KEY NOT NULL,
  "title" varchar(255) NOT NULL,
  "content" varchar(10000),
  "published" boolean DEFAULT false NOT NULL,
  "author_id" integer NOT NULL,
  "created_at" timestamp DEFAULT now() NOT NULL,
  CONSTRAINT "posts_author_id_users_id_fk" FOREIGN KEY ("author_id")
    REFERENCES "users"("id") ON DELETE no action ON UPDATE no action
);

Prisma Migrate

// prisma/schema.prisma — Prisma schema language
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String
  role      String   @default("user")
  createdAt DateTime @default(now())
  posts     Post[]

  @@index([email])
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  published Boolean  @default(false)
  author    User     @relation(fields: [authorId], references: [id])
  authorId  Int
  createdAt DateTime @default(now())
}
# Prisma Migrate commands

# Create and apply migration (dev)
npx prisma migrate dev --name add_posts_table
# Creates: prisma/migrations/20260308_add_posts_table/migration.sql
# Applies to dev DB automatically

# Apply migrations (production)
npx prisma migrate deploy

# Reset and re-apply all migrations (dev only — DANGEROUS in prod)
npx prisma migrate reset

# Check migration status
npx prisma migrate status

# Generate Prisma Client after schema change
npx prisma generate

Flyway (Polyglot, Explicit)

# Flyway — versioned SQL files
# db/migrations/V1__initial_schema.sql
# db/migrations/V2__add_posts_table.sql
# db/migrations/V3__add_email_index.sql

# Naming convention: V{version}__{description}.sql
# (Underscore-double-underscore separates version from description)
-- V2__add_posts_table.sql
CREATE TABLE posts (
  id SERIAL PRIMARY KEY,
  title VARCHAR(255) NOT NULL,
  content TEXT,
  published BOOLEAN NOT NULL DEFAULT false,
  author_id INTEGER NOT NULL REFERENCES users(id),
  created_at TIMESTAMP NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_posts_author_id ON posts(author_id);
# Flyway commands
flyway migrate          # Apply pending migrations
flyway info             # Show migration status
flyway validate         # Check migrations are applied correctly
flyway repair           # Fix failed migration checksums
flyway baseline         # Mark existing DB as at version X

Comparison

ToolSchema LanguageSQL VisibilityAuto-GenerationRollback
Drizzle KitTypeScript✅ FullManual SQL
Prisma MigratePrisma DSL✅ (shadow DB)Via preview
FlywayRaw SQL✅ Full❌ (you write)Undo scripts
Knex MigrateJS/TSPartialManual

When to Choose

ScenarioPick
Drizzle ORM projectDrizzle Kit
Prisma ORM projectPrisma Migrate
Need to review SQL before applyingDrizzle Kit or Flyway
Multi-language team (Java + Node)Flyway
Zero-schema tool, raw SQL preferredFlyway
Automatic schema generationDrizzle Kit or Prisma

Compare migration tool package health on PkgPulse.

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.