Migrating From Prisma to Drizzle ORM
Hitting Prisma's limits and the 2-week journey of switching to Drizzle ORM
When Prisma Started Getting Slow
Prisma was perfect in the early days. Define a schema, run npx prisma generate, and you get a type-safe client. But as our tables grew to 47 with complex relations, problems surfaced.
prisma generate started taking 12 seconds. Every hot reload after a code change triggers Prisma Client regeneration. 12 seconds. Multiply that by dozens of times a day and it's brutal. (More precisely, 12 seconds on normal days. On schema-change days, it took over 5 minutes and 40 seconds.)
Then there was query performance. I logged the SQL that Prisma generates and found it creating 4 subqueries for what should have been a simple join. Writing the SQL by hand would be a single JOIN.
Why I Picked Drizzle
I compared Drizzle, Kysely, and TypeORM. TypeORM was out based on past experience with decorator hell. Kysely is great as a query builder but its migration tooling was lacking.
Two things sold me on Drizzle. First, the SQL-like query syntax. Instead of Prisma's thick abstraction layer, writing Drizzle feels like writing SQL in TypeScript. Second, bundle size. Prisma Client adds significant weight to a project. Drizzle is much lighter.
// Prisma - hard to predict what SQL comes out
const users = await prisma.user.findMany({
where: { posts: { some: { published: true } } },
include: { posts: true, profile: true },
});
// Drizzle - the SQL is almost visible
const users = await db
.select()
.from(usersTable)
.leftJoin(postsTable, eq(usersTable.id, postsTable.userId))
.where(eq(postsTable.published, true));Two Weeks of Migration
I tried migrating everything at once and it fell apart. Moving 47 tables simultaneously was just too much. Switched to a gradual approach instead. Run Prisma and Drizzle side by side. New features use Drizzle, existing features migrate incrementally.
This works because both ORMs point at the same database. As long as schemas stay synchronized, no issues. But schema synchronization was trickier than expected. Prisma's @map and @@map directives rename tables and columns, and the Drizzle schema needs to match exactly. That cost me a day and a half.
The Transaction Mistake That Nearly Hurt
The most painful blunder was with transaction handling. Three places where Prisma's $transaction had been wrapping operations got migrated without the transaction in Drizzle. Caught in staging, thankfully. If it had reached production, we'd have had data consistency issues.
Lesson: when switching ORMs, list every place that uses transactions in the existing code, and verify each one after migration. I didn't do this, thinking "it'll be fine." Code review saved me.
Results After Two Weeks
generate time went from 12 seconds to 0. Drizzle doesn't generate code at runtime. Hot reloads are instant. This alone justified the migration.
Query performance improved by an average of 1.4x. The difference was most pronounced on complex join queries. When 4 subqueries become 1 JOIN, of course it's faster.
There are downsides. No GUI management tool like Prisma Studio. Drizzle Studio exists but it's still feature-light. And Prisma's include syntax was intuitive. Drizzle's join syntax requires some SQL knowledge. There was a learning curve for junior team members. The migration itself took 2 weeks, but getting the whole team comfortable with Drizzle took a month and a half.