{"id":302,"date":"2026-06-01T13:03:49","date_gmt":"2026-06-01T13:03:49","guid":{"rendered":"https:\/\/abrarqasim.com\/blog\/drizzle-vs-prisma-six-months-in-what-i-actually-use\/"},"modified":"2026-06-01T13:03:49","modified_gmt":"2026-06-01T13:03:49","slug":"drizzle-vs-prisma-six-months-in-what-i-actually-use","status":"publish","type":"post","link":"https:\/\/abrarqasim.com\/blog\/drizzle-vs-prisma-six-months-in-what-i-actually-use\/","title":{"rendered":"Drizzle vs Prisma: Six Months In, Here&#8217;s What I Actually Use"},"content":{"rendered":"<p>Confession: I spent two weeks last winter trying to convince myself that Prisma was still the right call for a side project I was rebuilding. The schema file looked clean, the type generation felt familiar, and I&rsquo;d shipped enough Prisma apps that I could do it half-asleep. Then I deployed it to a Cloudflare Worker, watched the cold-start times, and quietly started porting the data layer to Drizzle that same night.<\/p>\n<p>Six months later I&rsquo;ve used both in production on three different shapes of project: a Next.js app on Vercel, a small Hono API on Workers, and a long-running Node service on a Hetzner box. What I learned isn&rsquo;t &lsquo;Drizzle wins&rsquo;. It&rsquo;s that the choice has stopped being about features and started being about where your code runs, how comfortable you are reading SQL, and how much you care about your migrations behaving like, well, migrations.<\/p>\n<p>If you want the short answer: I default to Drizzle now, but Prisma is still the right tool for two specific cases. Read on if you want to know which two.<\/p>\n<h2 id=\"the-shape-problem-schema-first-vs-code-first\">The shape problem: schema-first vs. code-first<\/h2>\n<p>Both ORMs ask you to declare your schema once and get types and queries from it. They just disagree on what &lsquo;declare your schema&rsquo; means.<\/p>\n<p>In Prisma you write a <code>.prisma<\/code> file. It&rsquo;s a small DSL the team built to look like a stripped-down GraphQL schema, and you run <code>prisma generate<\/code> to turn it into a client. Here&rsquo;s a tiny example:<\/p>\n<pre><code class=\"language-prisma\">model Post {\n  id        Int      @id @default(autoincrement())\n  slug      String   @unique\n  title     String\n  body      String\n  published Boolean  @default(false)\n  createdAt DateTime @default(now())\n}\n<\/code><\/pre>\n<p>In Drizzle the schema is just TypeScript. No DSL, no codegen step, no separate language to learn:<\/p>\n<pre><code class=\"language-ts\">import { pgTable, serial, text, boolean, timestamp } from 'drizzle-orm\/pg-core';\n\nexport const posts = pgTable('posts', {\n  id: serial('id').primaryKey(),\n  slug: text('slug').notNull().unique(),\n  title: text('title').notNull(),\n  body: text('body').notNull(),\n  published: boolean('published').notNull().default(false),\n  createdAt: timestamp('created_at').notNull().defaultNow(),\n});\n<\/code><\/pre>\n<p>The Prisma file is shorter and reads almost like documentation. The Drizzle file is plain TS, so it composes with your existing tooling. You refactor across the codebase with the same Find References command you already use. You share types with your validation layer. You run codemods on it. After about a week of writing the TypeScript version I stopped missing the DSL. After two weeks I started liking that there was no codegen step to forget.<\/p>\n<h2 id=\"migrations-where-the-real-friction-lives\">Migrations: where the real friction lives<\/h2>\n<p>This is where I switched camps for good.<\/p>\n<p>Prisma&rsquo;s migration story is a wrapper around SQL. You change <code>schema.prisma<\/code>, run <code>prisma migrate dev<\/code>, and it writes a <code>migration.sql<\/code> file plus a <code>_prisma_migrations<\/code> tracking table. That works fine in development. In production it gets opinionated. The CLI wants to detect drift, wants to reset on conflict, and the failure modes when you&rsquo;re recovering from a partial deploy are not fun to debug at 2am.<\/p>\n<p>Drizzle uses <code>drizzle-kit<\/code> to generate migrations from your TS schema, but the output is a plain SQL file you can read, edit, commit, and apply yourself. Generation looks like this:<\/p>\n<pre><code class=\"language-bash\">npx drizzle-kit generate\n# writes drizzle\/0001_add_published_flag.sql\nnpx drizzle-kit migrate\n# applies pending migrations using a tiny tracking table\n<\/code><\/pre>\n<p>The SQL file is the source of truth. If <code>drizzle-kit<\/code> generates something I don&rsquo;t like, and it does occasionally for renames or partial unique indexes, I open the file and fix it before applying. There&rsquo;s no clever drift detection trying to outsmart me. The <a href=\"https:\/\/orm.drizzle.team\/docs\/migrations\" rel=\"nofollow noopener\" target=\"_blank\">Drizzle migrations docs<\/a> describe this as &lsquo;manual migrations with codegen&rsquo;, and that label is accurate.<\/p>\n<p>I&rsquo;m not the only person who finds the SQL-first approach calming. Even the official <a href=\"https:\/\/www.prisma.io\/docs\/orm\/prisma-migrate\/getting-started\" rel=\"nofollow noopener\" target=\"_blank\">Prisma migrate docs<\/a> recommend an <code>apply<\/code>-only workflow in production, which is essentially what Drizzle gives you by default. The gap closes once you wire your CI up carefully. But you have to wire it up carefully, and you don&rsquo;t with Drizzle.<\/p>\n<h2 id=\"typescript-inference-same-idea-very-different-output\">TypeScript inference: same idea, very different output<\/h2>\n<p>Both give you autocompletion and end-to-end types. The shape of the output is where they part ways.<\/p>\n<p>Prisma returns nested objects when you <code>include<\/code> relations:<\/p>\n<pre><code class=\"language-ts\">const post = await prisma.post.findUnique({\n  where: { slug: 'drizzle-vs-prisma' },\n  include: { author: true, tags: true },\n});\n\/\/ post.author.name, post.tags[0].label\n<\/code><\/pre>\n<p>Drizzle gives you two flavors. There&rsquo;s a SQL-like query builder that returns flat rows. You write the joins yourself, you read what comes back:<\/p>\n<pre><code class=\"language-ts\">const rows = await db\n  .select()\n  .from(posts)\n  .leftJoin(authors, eq(posts.authorId, authors.id))\n  .where(eq(posts.slug, 'drizzle-vs-prisma'));\n<\/code><\/pre>\n<p>And there&rsquo;s a relational query API that returns nested objects, much closer to Prisma&rsquo;s feel:<\/p>\n<pre><code class=\"language-ts\">const post = await db.query.posts.findFirst({\n  where: eq(posts.slug, 'drizzle-vs-prisma'),\n  with: { author: true, tags: true },\n});\n<\/code><\/pre>\n<p>The relational API was the thing that made Drizzle stop feeling like a step backward. I use it for most read paths because the inference is identical to Prisma&rsquo;s mental model. I drop down to the SQL builder when I want a specific join order, a CTE, a window function, or anything else where I want to see the exact query the database will run. The escape hatch is the actual point, for me.<\/p>\n<p>Prisma also has raw queries via <code>$queryRaw<\/code>, but the ergonomics aren&rsquo;t great and the types are looser. Drizzle&rsquo;s tagged-template <code>sql<\/code> helper is genuinely pleasant to read and types parameters correctly.<\/p>\n<h2 id=\"runtime-cost-cold-starts-bundle-size-edge-runtimes\">Runtime cost: cold starts, bundle size, edge runtimes<\/h2>\n<p>This is what pushed me over the edge for the Workers deploy.<\/p>\n<p>Prisma&rsquo;s ORM ships a query engine, historically a Rust binary that gets bundled with your app or accessed via Data Proxy or Accelerate. The team has been steadily moving toward a <a href=\"https:\/\/www.prisma.io\/blog\" rel=\"nofollow noopener\" target=\"_blank\">TypeScript-native Prisma client<\/a> and it&rsquo;s improved a lot, but on Cloudflare Workers and similar edge runtimes the binary path is still the dominant story for many setups. That means longer cold starts and a noticeable bundle hit.<\/p>\n<p>Drizzle is just TypeScript. No binary, no separate process. On the same Hono API I measured a roughly 4x cold-start improvement and a bundle smaller by tens of kilobytes when I replaced Prisma plus Accelerate with Drizzle plus <code>postgres.js<\/code>. Your mileage will vary, but the direction is consistent across every benchmark I&rsquo;ve seen people post on the <a href=\"https:\/\/github.com\/drizzle-team\/drizzle-orm\/discussions\" rel=\"nofollow noopener\" target=\"_blank\">Drizzle GitHub discussions<\/a>.<\/p>\n<p>The flip side: Drizzle gives you nothing for connection pooling. You wire it up yourself with <code>postgres<\/code>, <code>node-postgres<\/code>, <code>neon-http<\/code>, <code>@planetscale\/database<\/code>, whatever fits your runtime. Prisma at least pretends to manage connections for you. For a serverless app where I&rsquo;m already using a pooler at the database level, that&rsquo;s a wash. For a long-running Node service, Prisma&rsquo;s behavior is slightly more turn-key, though the difference is small if you&rsquo;ve ever configured a pool before.<\/p>\n<p>I wrote up the Postgres side of this in <a href=\"https:\/\/abrarqasim.com\/blog\/postgres-jsonb-2026-when-i-reach-for-it\" rel=\"noopener\">my notes on jsonb defaults<\/a>, because the ORM choice and the database choice keep colliding in ways I didn&rsquo;t expect when I started.<\/p>\n<h2 id=\"what-about-json-rls-and-the-awkward-bits\">What about JSON, RLS, and the awkward bits<\/h2>\n<p>A few specific areas where the two diverge in ways that matter to me.<\/p>\n<p>JSON columns: Drizzle&rsquo;s <code>jsonb<\/code> type takes a generic that types the contents, which is what I want most of the time. Prisma still returns <code>Prisma.JsonValue<\/code>, and you cast or validate at the boundary. Neither is wrong; Drizzle&rsquo;s is what I reach for.<\/p>\n<p>Row-level security: if you&rsquo;re leaning on Postgres RLS the way I do for multi-tenant apps, you want fine control over the session settings. Both ORMs can do it, but with Drizzle I&rsquo;m closer to the wire, which makes things like <code>set_config('app.user_id', ...)<\/code> simpler to thread through. Prisma&rsquo;s session-variable story improved in recent releases, though it still feels like a workaround.<\/p>\n<p>Studio and seeding: Prisma Studio is genuinely good. Drizzle Studio has caught up faster than I expected but still has rough edges around enum editing and large tables. For seed scripts I prefer Drizzle (it&rsquo;s just TS calling <code>db.insert<\/code>), but <code>prisma db seed<\/code> is more standardized if your team needs a convention.<\/p>\n<p>Logging: Drizzle&rsquo;s <code>logger<\/code> interface gives me the SQL with parameters out of the box, which is what I want during incidents. Prisma&rsquo;s <code>event.query<\/code> is decent but the format is noisier. This sounds like a small thing until you&rsquo;re tailing logs on a Friday afternoon.<\/p>\n<p>If you&rsquo;re doing serious work, the kind I cover in <a href=\"https:\/\/abrarqasim.com\/work\" rel=\"noopener\">my portfolio of full-stack projects<\/a>, these awkward bits add up faster than you&rsquo;d guess.<\/p>\n<h2 id=\"so-which-one-do-i-actually-reach-for-now\">So which one do I actually reach for now?<\/h2>\n<p>Drizzle by default, especially for anything serverless or edge-deployed. The combination of plain TypeScript schemas, SQL-first migrations, and a tiny runtime is the closest thing to &lsquo;ORM without regret&rsquo; I&rsquo;ve used.<\/p>\n<p>Prisma still wins for two cases I keep hitting:<\/p>\n<ol>\n<li>\n<p>A team that doesn&rsquo;t read SQL comfortably. Prisma&rsquo;s DSL plus the nested query API plus Studio is the gentlest on-ramp from &lsquo;I know JavaScript&rsquo; to &lsquo;I&rsquo;m shipping a database-backed app&rsquo;. I&rsquo;ve onboarded juniors with Drizzle and they get there, but it takes longer.<\/p>\n<\/li>\n<li>\n<p>Apps that depend on Prisma-specific tooling. Accelerate for caching, Pulse for change streams, a Studio-driven workflow your PM uses every day. That ecosystem is real, and pulling out of it is more painful than the ORM itself.<\/p>\n<\/li>\n<\/ol>\n<p>If you&rsquo;re on the fence: pick a small read path in your current app, write it in both, then deploy both versions and watch the latency for a week. The right answer will show up faster than any blog post can tell you. Then come back and yell at me on <a href=\"https:\/\/abrarqasim.com\" rel=\"noopener\">my contact page<\/a> if I got it wrong.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Drizzle vs Prisma in 2026: my honest take after six months in production on Next.js, Hono on Workers, and one boring Node service on a Hetzner box.<\/p>\n","protected":false},"author":2,"featured_media":301,"comment_status":"","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"","rank_math_description":"Drizzle vs Prisma in 2026: my honest take after six months in production on Next.js, Hono on Workers, and one boring Node service on a Hetzner box.","rank_math_focus_keyword":"drizzle vs prisma","rank_math_canonical_url":"","rank_math_robots":"","footnotes":""},"categories":[35],"tags":[180,61,182,177,181,63],"class_list":["post-302","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-web-development","tag-drizzle","tag-nextjs","tag-orm","tag-postgres","tag-prisma","tag-typescript"],"_links":{"self":[{"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/posts\/302","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/comments?post=302"}],"version-history":[{"count":0,"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/posts\/302\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/media\/301"}],"wp:attachment":[{"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/media?parent=302"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/categories?post=302"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/tags?post=302"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}