← Semua picks

Migration Guide Recommended

Migrasi Prisma → Drizzle 90 Menit

Saya migrate 4 project klien Prisma → Drizzle 2025-2026. Average 90 menit per project. Bundle 6x lebih kecil, edge-compatible. Guide step-by-step plus gotcha.

15 Juni 2026 · 10 menit ·Use case: Migrate ORM SaaS Indonesia tanpa downtime
PrismaDrizzlePostgresTypeScript

TL;DR

  • Recommended migrate Prisma → Drizzle untuk project edge-first atau bundle-sensitive.
  • Effort: 60-120 menit untuk schema 15-25 table (saya validate 4x).
  • Gain: bundle 6x lebih kecil, cold start 5-10x lebih cepat, edge-compatible.

Konteks

Saya migrate 4 project klien Prisma → Drizzle Q4 2025 - Q2 2026:

  1. SaaS dental Jakarta (Next.js 15, 18 table): 95 menit migration + 3 hari testing
  2. Ecommerce klinik kecantikan (Astro + Hono backend, 22 table): 110 menit + 2 hari testing
  3. AI legal assistant (Next.js 15 + Cloudflare Workers, 12 table): 75 menit + 2 hari testing (motivation: edge compat)
  4. Internal dashboard HR (Next.js + Vercel, 28 table): 130 menit + 4 hari testing

Average migration: 102 menit. Average testing: 3 hari. Zero rollback. Drizzle vs Prisma vs Kysely detail.

Pricing

Prisma dan Drizzle sama-sama gratis. Migration cost = waktu dev.

  • Solo dev senior (Rp 250rb/jam): 102 menit = Rp 425rb
  • Testing 3 hari × 4 jam = 12 jam × Rp 250rb = Rp 3 juta

Total migration cost: Rp 3.4 juta untuk project medium.

ROI: cold start saving 300-800ms × 50K invocation/bulan = save UX delay. Plus bundle size reduction membantu edge deployment (yang tidak possible dengan Prisma).

Why migrate

1. Edge compatibility

Prisma butuh Query Engine binary native — tidak work di Cloudflare Workers atau Vercel Edge runtime tanpa Driver Adapter (beta). Drizzle native edge.

2. Bundle size

  • Prisma client: 1.5MB+ (Query Engine + generated code)
  • Drizzle: 250KB total

Untuk CF Workers (1MB limit untuk paid plan), Prisma struggle.

3. Cold start

  • Prisma cold start: 300-800ms
  • Drizzle cold start: 30-80ms

5-10x improvement. Significant untuk UX serverless.

4. Codegen overhead

Prisma butuh prisma generate setiap schema change. Forget = type out of sync. Drizzle: no codegen, type inferred from schema TS file.

5. SQL control

Drizzle syntax SQL-like. Bisa write raw SQL fragment dengan type safety. Prisma abstract SQL — harder debug complex query.

Step-by-step (90 menit average)

Step 1: Setup branch + backup (5 menit)

git checkout -b migrate/drizzle
pg_dump $DATABASE_URL > backup-pre-drizzle.sql

Step 2: Install Drizzle (5 menit)

bun add drizzle-orm pg
bun add -D drizzle-kit @types/pg

Atau untuk Neon/Supabase/Cloudflare D1:

# Neon
bun add @neondatabase/serverless
# Supabase (postgres-js)
bun add postgres
# Cloudflare D1
# (D1 driver bawaan Wrangler)

Step 3: Convert schema (30-40 menit)

Prisma schema:

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

model Post {
  id        String   @id @default(cuid())
  userId    String
  user      User     @relation(fields: [userId], references: [id])
  title     String
  published Boolean  @default(false)
}

Drizzle equivalent (src/db/schema.ts):

import { pgTable, text, timestamp, boolean } from 'drizzle-orm/pg-core';
import { createId } from '@paralleldrive/cuid2';
import { relations } from 'drizzle-orm';

export const users = pgTable('User', {
  id: text('id').primaryKey().$defaultFn(() => createId()),
  email: text('email').notNull().unique(),
  name: text('name').notNull(),
  role: text('role').default('user').notNull(),
  createdAt: timestamp('createdAt').defaultNow().notNull(),
});

export const posts = pgTable('Post', {
  id: text('id').primaryKey().$defaultFn(() => createId()),
  userId: text('userId').notNull().references(() => users.id),
  title: text('title').notNull(),
  published: boolean('published').default(false).notNull(),
});

// Relations untuk query relational API
export const usersRelations = relations(users, ({ many }) => ({
  posts: many(posts),
}));

export const postsRelations = relations(posts, ({ one }) => ({
  user: one(users, { fields: [posts.userId], references: [users.id] }),
}));

Penting: gunakan pgTable('User', ...) (capitalized) match exactly table name Prisma generate. Cek _prisma_migrations atau database introspection.

Untuk schema besar (20+ table): pakai Claude Code untuk batch convert. Prompt: “Convert this Prisma schema to Drizzle, preserve table names exactly, include relations API.”

Step 4: Setup Drizzle config (5 menit)

drizzle.config.ts:

import { defineConfig } from 'drizzle-kit';

export default defineConfig({
  schema: './src/db/schema.ts',
  out: './drizzle',
  dialect: 'postgresql',
  dbCredentials: { url: process.env.DATABASE_URL! },
});

Step 5: Baseline migration (10 menit)

# Pull current DB state ke Drizzle migration history
bunx drizzle-kit pull

# Atau jika prefer fresh start:
# Drop _prisma_migrations table di DB
# bunx drizzle-kit generate (creates initial migration from schema TS)

Step 6: Refactor query (30-45 menit)

Setup DB client (src/db/index.ts):

import { drizzle } from 'drizzle-orm/node-postgres';
import { Pool } from 'pg';
import * as schema from './schema';

const pool = new Pool({ connectionString: process.env.DATABASE_URL });
export const db = drizzle(pool, { schema });

Untuk edge runtime (Cloudflare Workers + Neon):

import { drizzle } from 'drizzle-orm/neon-http';
import { neon } from '@neondatabase/serverless';
import * as schema from './schema';

const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql, { schema });

Refactor query example:

PrismaDrizzle
prisma.user.findUnique({ where: { id } })db.select().from(users).where(eq(users.id, id))
prisma.user.findUnique({ where: { id }, include: { posts: true } })db.query.users.findFirst({ where: eq(users.id, id), with: { posts: true } })
prisma.user.create({ data: {...} })db.insert(users).values({...}).returning()
prisma.user.update({ where: { id }, data: {...} })db.update(users).set({...}).where(eq(users.id, id))
prisma.user.delete({ where: { id } })db.delete(users).where(eq(users.id, id))

Pakai Claude Code untuk batch refactor: “Refactor semua Prisma query di src/ menjadi Drizzle equivalent. Keep behavior identik.”

Step 7: Remove Prisma (5 menit)

bun remove prisma @prisma/client
rm -rf prisma/  # Atau preserve untuk reference

Update env: DATABASE_URL tetap sama format.

Step 8: Test (variable, 1-3 hari)

# Run typecheck
bun run typecheck

# Run unit test
bun test

# Run integration test
bun test:integration

# Manual smoke test di staging

Critical path test:

  • Auth flow (signup, login, logout)
  • CRUD operation untuk resource utama
  • Complex query (filter, sort, paginate)
  • Transaction (jika ada)
  • Migration: ensure no schema drift

Performance gain (4 project)

Cold start

ProjectPrismaDrizzleSpeedup
SaaS dental580ms75ms7.7x
Ecommerce klinik720ms95ms7.6x
AI legal480ms (degraded due to edge)45ms (native edge)10.7x
Dashboard HR650ms85ms7.6x

Bundle size

ProjectPrisma clientDrizzle
SaaS dental1.6 MB245 KB
Ecommerce klinik1.7 MB280 KB
AI legal1.5 MB220 KB
Dashboard HR1.8 MB310 KB

Consistent 6-7x bundle reduction.

Query latency

Similar (database = bottleneck, not ORM). Drizzle marginal lebih cepat untuk simple query karena no engine overhead.

Common gotcha

1. Cuid/uuid generation

Prisma @default(cuid()) generated at DB level via Prisma. Drizzle: pakai $defaultFn(() => createId()) (client-side) atau setup Postgres extension untuk uuid native.

2. DateTime field

Prisma DateTime → Drizzle timestamp({ mode: 'date' }). Watch out untuk timezone handling — explicit set ke ‘date’ mode untuk JS Date object.

3. JSON column

Prisma Json field → Drizzle jsonb('field').$type<MyType>(). Type cast required.

4. Relation include vs query API

Prisma include: { posts: true } mostly straightforward to Drizzle with: { posts: true }. Tapi nested deep include syntax beda.

5. Raw query

Prisma prisma.$queryRaw → Drizzle db.execute(sql\…`)`. Beda template literal syntax.

6. Migration history

Jangan biarkan kedua tool (Prisma + Drizzle) jalan di DB sama. Pilih satu — drop migration history tool lain.

Konteks Indonesia

SaaS dental Jakarta: migrate motivated oleh edge deploy goal. Setelah Drizzle: bisa migrate ke Cloudflare Workers. Cold start 580ms → 45ms. Customer experience noticeable improvement.

Ecommerce klinik kecantikan: migrate untuk bundle size — Astro frontend + Hono backend di CF Workers. Prisma sebelumnya force backend ke VPS. Setelah Drizzle: full edge deploy, bill turun Rp 600rb/bulan → Rp 100rb/bulan.

AI legal assistant: migrate primary motivation karena Prisma tidak work di CF Workers (Driver Adapter masih beta). Drizzle native edge — feature jalan production tanpa workaround.

Verdict

Recommended migrate Prisma → Drizzle untuk:

Migrate jika:

  • Deploy di edge (CF Workers, Vercel Edge, Bun)
  • Cold start sensitive untuk UX
  • Bundle size matter (mobile + slow connection)
  • Butuh kontrol SQL granular
  • Pakai stack modern (Hono, Astro, Bun)

Stay Prisma jika:

  • Project legacy stable, no active feature work
  • Team familiar Prisma + butuh Prisma Studio GUI heavy
  • Schema introspection auto important (Prisma menang)
  • Multi-database (Mongo + Postgres) — Prisma cross-DB support lebih matang
  • Deploy hanya di Node.js server (no edge)

Migration playbook:

  1. Validate need (audit bill, cold start metric, edge deploy goal)
  2. Test migration di branch + staging dulu
  3. Plan testing 2-4 hari sebelum cutover
  4. Cutover saat low-traffic window

Untuk ORM comparison lebih dalam: Drizzle vs Prisma vs Kysely. Untuk RSC pattern: Drizzle vs Kysely vs Prisma di RSC 2026.

Ditulis oleh Asti Larasati

// Pick Migration Guide lain


← Semua picks RSS feed