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 Kit —
drizzle-kit generatecreates SQL;drizzle-kit pushapplies (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
| Tool | Schema Language | SQL Visibility | Auto-Generation | Rollback |
|---|---|---|---|---|
| Drizzle Kit | TypeScript | ✅ Full | ✅ | Manual SQL |
| Prisma Migrate | Prisma DSL | ✅ (shadow DB) | ✅ | Via preview |
| Flyway | Raw SQL | ✅ Full | ❌ (you write) | Undo scripts |
| Knex Migrate | JS/TS | Partial | ❌ | Manual |
When to Choose
| Scenario | Pick |
|---|---|
| Drizzle ORM project | Drizzle Kit |
| Prisma ORM project | Prisma Migrate |
| Need to review SQL before applying | Drizzle Kit or Flyway |
| Multi-language team (Java + Node) | Flyway |
| Zero-schema tool, raw SQL preferred | Flyway |
| Automatic schema generation | Drizzle Kit or Prisma |
Compare migration tool package health on PkgPulse.
See the live comparison
View drizzle vs. prisma on PkgPulse →