Skip to content

Drizzle vs Prisma in 2026: When I Actually Switched (and When I Didn’t)

Drizzle vs Prisma in 2026: When I Actually Switched (and When I Didn’t)

I switched a side project from Prisma to Drizzle last August, and then I switched a client project from Drizzle back to Prisma in February. So if you’re looking for “Drizzle is better, here are five reasons,” this isn’t that post. They’re both fine. The trick is figuring out which one fits the project in front of you, and that took me eight months of running both in production to actually understand.

Short version for the impatient: pick Drizzle if you care about bundle size, raw SQL control, and edge runtime support. Pick Prisma if your team wants the friendliest schema-first DX and you’re shipping on a long-lived Node server where the runtime cost doesn’t show up in your bill. If you want to know how I actually decided each time, read on.

What changed for me between Prisma and Drizzle

I’d been on Prisma since late 2022. Schema-first, generated client, migrate dev, the whole thing. It was fine. The thing that pushed me to try Drizzle was bundle size, specifically when I started shipping more code to Vercel Edge and Cloudflare Workers. The Prisma client (with its query engine binary) is not built for those runtimes the way Drizzle’s pure-TS approach is. I’d hit cold-start issues that I was patching around with workarounds I didn’t love.

Drizzle felt like writing SQL again, which I’d missed. Here’s the same query in both, against a basic posts table joined to users:

// Prisma
const posts = await prisma.post.findMany({
  where: { published: true },
  include: { author: { select: { id: true, name: true } } },
  orderBy: { createdAt: 'desc' },
  take: 10,
});
// Drizzle
const rows = await db
  .select({
    id: postsTable.id,
    title: postsTable.title,
    createdAt: postsTable.createdAt,
    author: { id: usersTable.id, name: usersTable.name },
  })
  .from(postsTable)
  .leftJoin(usersTable, eq(postsTable.authorId, usersTable.id))
  .where(eq(postsTable.published, true))
  .orderBy(desc(postsTable.createdAt))
  .limit(10);

The Prisma version is shorter. Reads like a config object. The Drizzle version is more code, but you can almost see the SQL it’ll generate just by squinting at it. For a junior dev hitting the codebase cold, the Prisma one is faster to grok. For me, debugging a slow query at 11pm, the Drizzle one is faster to fix.

Where Drizzle actually wins for me

I keep three things on a sticky note above my desk, because I forget them otherwise.

Bundle size. On a Cloudflare Worker, the Drizzle bundle is roughly 100KB. Prisma’s accelerate-compatible client is bigger, and the standard client with the Rust query engine isn’t really designed for Workers at all. If you’re shipping serverless functions or edge code, Drizzle removes a category of pain that Prisma keeps trying to solve and not quite solving.

Edge runtime support. Drizzle works with Neon, PlanetScale, Turso, Cloudflare D1, and Vercel Postgres in the edge runtime, with no extra layer. The Drizzle docs on serverless drivers walk through it, and it’s the kind of setup where I genuinely just followed the page and shipped. Prisma’s edge story has gotten better with Prisma Accelerate, but you’re paying for a connection pool service to make it work.

Raw SQL escape hatches feel native, not bolted on. When you need a window function, a CTE, or ON CONFLICT DO UPDATE, Drizzle gives you a SQL template tag that’s just SQL with parameter binding:

import { sql } from 'drizzle-orm';

await db.execute(sql`
  INSERT INTO views (post_id, day, count)
  VALUES (${postId}, current_date, 1)
  ON CONFLICT (post_id, day) DO UPDATE
  SET count = views.count + 1
`);

With Prisma I’d find myself dropping into $queryRaw more often than I wanted, and at that point I’m half on the ORM and half off it.

Where Prisma still wins for me

OK, it’s not all on Drizzle’s side. There are three places where Prisma still does it better, and I have to keep reminding myself so I don’t get carried away.

Migrations. Prisma’s migration story is the cleanest one I’ve used in TypeScript. prisma migrate dev reads your schema, diffs against the DB, generates a SQL migration, applies it. Drizzle’s migration tooling has gotten much better in 2025, but it’s not at the same level. With Drizzle I still occasionally hand-edit a generated migration when the diff isn’t quite what I want. With Prisma I almost never do.

Schema readability. The schema.prisma file is genuinely nice to skim:

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

  @@index([published, createdAt])
}

Pull up a Prisma project you haven’t touched in six months, open schema.prisma, and you can see your data model in two minutes. Drizzle’s schema is plain TypeScript, which is more flexible but harder to read at a glance. On a small team that’s a wash. On a team where someone less familiar with TypeScript has to read the schema, Prisma wins.

Tooling and docs. Prisma Studio is a real tool that real people use. The error messages are written for humans. The docs are exhaustive. Drizzle’s docs have improved a lot, but if I’m onboarding a new dev who’s never seen either ORM, I can hand them the Prisma docs and they’ll be productive in a day.

The actual switching cost (this is the bit nobody talks about)

Switching ORMs sounds easy if you’ve never done it. It isn’t.

When I moved my side project from Prisma to Drizzle, the schema rewrite took me about an hour. The query rewrites took me a weekend, because every .findMany with include had to become a select with a leftJoin, and I had to think about each one. The third bucket was the part I didn’t expect: types. Prisma generates types like Post & { author: User } automatically. With Drizzle you build the return type yourself, or you use $inferSelect, or you pull from your select clause. It’s not bad, just different, and your existing type signatures don’t carry over for free.

If your codebase has 200 queries, plan on a week of work, not an afternoon. If you’re at 50 queries, two days. I’ve seen teams underestimate this and resent the migration halfway through. Don’t be that team. I learned this the hard way, which is also how I learned to be careful with Next.js server actions — that migration cost story is the same shape: looks easy, isn’t.

My current rule of thumb

When I start a new project in 2026, my decision tree looks roughly like this. New Next.js 15 app on Vercel Edge or Cloudflare? Drizzle. New Node server with a long-lived runtime, no edge constraints, mid-sized team? Prisma. Existing Prisma codebase that’s working fine? Don’t migrate just to migrate. Existing Drizzle codebase where someone keeps writing terrible joins? Add a code review checklist before you blame the ORM.

For client work I have a simpler rule: I pick the one the existing team already knows. The cost of an ORM switch on a project I don’t own day-to-day is rarely worth it. I’d rather optimize the slow queries than relitigate the data layer.

If you want to see how this kind of thing plays out in production code, I keep a few of these patterns in my work, where I’ve been writing both side by side for the last year.

What to try this week

Pick one query in your current ORM that you’ve been unhappy with. Maybe it’s the one that needs an index hint, or the one with three nested includes that’s secretly running five queries. Write the same thing in the other ORM. Don’t migrate the whole codebase, just write one query both ways and look at the SQL each one produces. Drizzle has a .toSQL() method that prints the generated SQL. Prisma has the query log you can turn on with log: ['query']. Run both, compare, decide.

That’s the test I wish I’d run a year ago. It would have saved me a weekend.