Skip to content

Drizzle vs Prisma: why we run both in production

For CTOs and engineersCTO decisions · Updated

Filip PapranecFounder, Digitaliko

Drizzle vs Prisma: one schema, two ORMs. Illustration: a schema.prisma window next to a schema.ts window with a sqliteTable definition, both pointing to a SQL migration file.Drizzle vs Prisma: one schema, two ORMs. Illustration: a schema.prisma window next to a schema.ts window with a sqliteTable definition, both pointing to a SQL migration file.

Drizzle vs Prisma is not a tie for us, and not a winner either. We run both in production: Prisma on Postgres in older client apps, Drizzle on Cloudflare D1 in every new app since we moved new work to Workers. The short answer: pick Drizzle when you deploy to a serverless or edge runtime and want the schema and migrations as plain TypeScript and SQL, keep Prisma where it already works.

Most comparisons are written by one of the two vendors or by someone who built a demo twice. This one comes from repositories we maintain, with the config as it is.

Drizzle vs Prisma: what is the actual difference?

Both give you a typed database client in TypeScript. The difference is where the schema lives and how far the ORM sits from SQL.

PrismaDrizzle
Schemaschema.prisma, its own language.ts files, plain TypeScript
ClientGenerated by prisma generateThe schema file is the type, no generate step for queries
QueriesObject API (findMany, include)SQL-shaped (select().from().where()) plus a relational API
Migrationsprisma migrate dev / deploydrizzle-kit generate writes SQL files, you apply them
Where we run itPostgres in older Next.js appsCloudflare D1 (SQLite) in new apps
EngineRust-free client since Prisma 7"exactly 0 dependencies" per its docs

The Drizzle docs describe it as "dialect-specific, slim, performant and serverless-ready by design" (Drizzle overview, checked 2026-10-24). Prisma is the higher-level tool: you describe models and it decides the SQL.

Why do new apps run on Drizzle?

Our new apps run on Cloudflare Workers with D1, which is SQLite. Our starter template and our internal tools share the same setup. The whole Drizzle config is this:

// packages/db/drizzle.config.ts
import { defineConfig } from 'drizzle-kit'

export default defineConfig({
  schema: './src/schema',
  out: './src/migrations',
  dialect: 'sqlite',
  driver: 'd1-http',
})

The schema is a TypeScript file. Trimmed from our starter:

// packages/db/src/schema/records.ts
import { sql } from 'drizzle-orm'
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'
import { user } from './auth'

const record = sqliteTable('record', {
  id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
  name: text('name').notNull(),
  status: text('status', { enum: ['active', 'inactive', 'pending'] }).default('active').notNull(),
  createdById: text('created_by_id').references(() => user.id, { onDelete: 'set null' }),
  createdAt: integer('created_at', { mode: 'timestamp_ms' })
    .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
    .notNull(),
})

The client is one line on top of the D1 binding, and a query reads like SQL:

import { drizzle } from 'drizzle-orm/d1'
import * as schema from './schema'

const createDb = () => drizzle(env.DB, { schema })

const [total] = await ctx.db.select({ count: count() }).from(record)

Three reasons made us switch for new work:

  • The runtime. D1 and Workers were the target. Drizzle connects with import { drizzle } from 'drizzle-orm/d1' (Drizzle and D1).
  • The agent reads the schema directly. AI writes all of our code today, and a senior is accountable for production. A schema in TypeScript is just code: the agent edits it, the type checker catches a wrong column at once, and there is no generated client to forget to regenerate.
  • Migrations are SQL in the PR. The reviewer sees the exact ALTER TABLE, not a diff of a model file.

Auth uses the same database. Better Auth plugs in with drizzleAdapter(db, { provider: 'sqlite' }), so users and sessions are ordinary tables next to the app's own.

Drizzle vs Prisma side by side. Left, Prisma in older apps: schema.prisma, prisma generate, prisma migrate deploy in the build, Postgres. Right, Drizzle in new apps: schema.ts in TypeScript, drizzle-kit generate writes a SQL file, the deploy applies it to Cloudflare D1. Footer: new apps on Drizzle, working Prisma apps stay.Drizzle vs Prisma side by side. Left, Prisma in older apps: schema.prisma, prisma generate, prisma migrate deploy in the build, Postgres. Right, Drizzle in new apps: schema.ts in TypeScript, drizzle-kit generate writes a SQL file, the deploy applies it to Cloudflare D1. Footer: new apps on Drizzle, working Prisma apps stay.

How does a schema change reach production?

A change goes the same way in every new app. Nobody runs SQL by hand against production.

  1. Change the schema in TypeScript

    Add a column or a table in packages/db/src/schema. The type checker flags every query that no longer fits.

  2. Generate the migration

    drizzle-kit generate writes a numbered SQL file into packages/db/src/migrations.

  3. Review the SQL in the PR

    Schema change and SQL file ship together, so the reviewer reads the real statement.

  4. Deploy applies it to D1

    Our infrastructure code points D1 at the migrations folder and applies new files on deploy.

The deploy side is in our infrastructure code, written in TypeScript with Alchemy:

// packages/infra/alchemy.run.ts (trimmed)
const db = await D1Database('database', {
  migrationsDir: '../../packages/db/src/migrations',
})

const web = await TanStackStart('web', {
  cwd: '../../apps/web',
  bindings: { DB: db },
})

One folder of SQL files is the history of the database. That is also what makes a handover readable for the next engineer.

Where does Prisma still win, and why do we keep it?

Several older client apps run Prisma on Postgres, across several Prisma major versions. One of them is the system we built and run for a freight forwarder. Most use the classic setup: prisma-client-js, NextAuth with the Prisma adapter, and prisma migrate deploy in the build script, so every deploy brings the database up to date before next build.

That setup works. Prisma's model language is easy to read for someone who does not write SQL every day, and its migration workflow is mature. We see no reason to remove it from an app that serves users well.

Swapping the ORM touches every query, every test and every migration at once, and the user sees nothing new. That risk only pays off when something else forces it, such as a move to Workers. When we take over an older app, its ORM is rarely the first thing worth fixing. How we pick what comes first is in Technical debt slowing development: what to fix first.

One more honest note: a client's online marketplace runs on MongoDB with Mongoose, so neither ORM is in that one.

What changed with Prisma 7?

A lot of "Drizzle vs Prisma" posts still argue about the Rust engine binary. That argument is weaker now. Prisma ORM 7 was released on 2025-11-19 with the "Rust-free Prisma Client as the default" (Prisma changelog, checked 2026-10-24). The upgrade guide says:

Upgrade to the new prisma-client provider which uses the new Rust-free client.

Prisma docs
Upgrade to Prisma ORM 7 (checked 2026-10-24)

The same guide says a driver adapter is now required for every database. So Prisma 7 also runs on Workers, and on D1 through @prisma/adapter-d1. Prisma's own D1 page adds a warning that holds for any ORM: D1 does not support transactions, so "implicit & explicit transactions will be ignored and run as individual queries" (Prisma and Cloudflare D1, checked 2026-10-24).

Honestly: we have not benchmarked Drizzle against Prisma 7, and we have not run Prisma 7 on D1. Most of our Prisma apps still use prisma-client-js; the newest one already runs the new prisma-client generator. If you compare the two today, compare against Prisma 7, not the version from the old blog posts.

Drizzle or Prisma for your next app?

Go through the points. The more of them are true for you, the better Drizzle fits:

Whichever you pick, keep the migrations in the repository and apply them from the deploy, never by hand. When a rewrite is on the table anyway, read Refactor or rewrite from scratch? How to decide first.

Want a web app with a data layer the next engineer can read?

We build web apps on this stack, Drizzle on D1 for new work, and we take over Prisma apps without rewriting what works: custom web application development. AI writes all of our code today, and a senior is accountable for the result. Since 2023 we have delivered 15+ production projects.

We get back to you within two business days, and together we pick what to fix first.

If you'd rather talk first, book a 15-minute meeting with Filip.

Which ORM is your team arguing about right now?

In short

  • We run both: Prisma on Postgres in older apps, Drizzle on Cloudflare D1 in every new app.
  • Drizzle keeps the schema in TypeScript and migrations as SQL files that go through review.
  • Prisma 7 dropped the Rust engine, so the old binary argument no longer decides it.
  • We never rewrite a working Prisma app just to change the ORM.

Frequently asked questions

Is Drizzle better than Prisma?

Neither is better in general. Drizzle fits serverless and edge runtimes like Cloudflare Workers with D1, keeps the schema in plain TypeScript and stays close to SQL. Prisma fits teams that want a higher-level client, its own migration workflow and tooling such as Prisma Studio, typically on Postgres.

Does Prisma still need a Rust engine binary?

Not since Prisma ORM 7, released on 2025-11-19. The new prisma-client generator uses a Rust-free client and needs a driver adapter for every database. Apps still on prisma-client-js with Prisma 5 or 6 use the Rust engine by default.

Can Prisma run on Cloudflare D1?

Yes, through the @prisma/adapter-d1 driver adapter. Prisma docs warn that D1 does not support transactions, so implicit and explicit transactions run as individual queries. The same D1 limit applies to every ORM.

Should we migrate an existing Prisma app to Drizzle?

Usually not. A rewrite of the data layer in a working app carries real risk and gives users nothing new. We move to Drizzle when we start a new app or when the runtime changes, for example to Cloudflare Workers.

How do Drizzle migrations work?

You change the schema in TypeScript, drizzle-kit generate writes a plain SQL migration file, and that file is applied on deploy. In our apps the SQL file goes through code review in the same PR as the schema change.

Ask AI about this article

The AI also draws on our other articles and experience from real projects.

ChatGPTClaudePerplexity
Engineering

Other articles

CTO decisions

Claude Code pricing for a team: plans, API and agents

Claude Code pricing from Anthropic's public list (October 2026): Pro, Max, Team seats, Enterprise and API tokens. Which one fits developers, which one an always-on agent and a CI reviewer run on, and the guardrails that stop a background agent from burning the budget.

CTO decisions

Vercel alternatives: why we run Vercel and Cloudflare

We did not replace Vercel, we split the work. Next.js sites stay on Vercel, new apps run on Cloudflare Workers and D1, declared in TypeScript with Alchemy. Pricing with sources, our config, and the deploy block an AI agent hit on Vercel.

CTO decisions

Better Auth vs Clerk: why we chose Better Auth

Better Auth vs Clerk comes down to where your users and sessions live. We moved from NextAuth to Better Auth in every new app and never picked Clerk. Our real config, the plugins we run, Clerk pricing and when Clerk is the better call.

Time to stop planning and start solving.

Contact

  • Digitaliko s.r.o.
    Šípková 377/17
    958 03 Malé Uherce, Slovakia
  • Get in touch
    info@digitaliko.com
    +421 950 595 506
    WhatsApp