Skip to main content
Logo
Overview

Drizzle vs Prisma vs Kysely 2026: Which TypeScript ORM?

May 4, 2026
13 min read

The Argument Reopened

If you’d asked me which TypeScript ORM to start a new project with twelve months ago, I would have shrugged and said “probably Drizzle, unless your team really wants prisma.schema.” Easy answer. Prisma was bleeding mindshare, the Rust query engine was famously chunky on serverless, and Drizzle’s edge story was just better.

Then two things happened.

Prisma 7 shipped in late 2025 and ripped the Rust engine out entirely. The client is now pure TypeScript, the bundle dropped from roughly 14 MB down to under 2 MB, and cold starts got a real, measurable boost. PlanetScale turned around and acquired Drizzle’s core team in March 2026. Suddenly the “Drizzle is a side project” complaint evaporated, and the “Prisma is too heavy for the edge” complaint did too. Both of the easy reasons to pick a side just went away.

So the question is fresh again. And Kysely — the third option that the loudest comparison posts keep ignoring — is sitting in the corner with an obnoxiously good answer for teams that just want SQL with types. Let me walk through how I’d think about this in mid-2026, with the new information actually factored in.

The Three Philosophies, Honestly

Pick an ORM and you’re really picking how much SQL you want to write yourself.

Prisma is schema-first. You define your models in prisma.schema, run prisma generate, and get a fully typed client where you write prisma.user.findMany({ where: { posts: { some: { published: true } } } }). The schema file is a single source of truth, the migration tool is opinionated and good, and the mental model is “database as data graph.” If you’ve ever liked an ORM, you’ll probably like Prisma.

Drizzle is code-first and SQL-adjacent. Your schema lives in TypeScript files (pgTable("users", { ... })), and your queries look like SQL with type safety glued on top: db.select().from(users).where(eq(users.id, 1)). There’s a “relational queries” API for nested fetches that feels more ORM-like, but the default dialect is “SQL you can read.” No code generation step, just types flowing from your schema definitions.

Kysely is not an ORM at all. It’s a typed query builder. You give it your database schema as a TypeScript type, and you build queries with a fluent API that maps almost one-to-one to SQL: db.selectFrom("users").where("id", "=", 1).selectAll().execute(). There are no relations, no eager-loading helpers, no migration tool. You pair it with kysely-codegen (or kanel, or pg-to-ts) for the schema types and kysely-migrator or your own SQL files for migrations.

Which one is “right” depends almost entirely on how much you trust your team to write SQL and how much you want a single tool to handle the whole data layer.

Bundle Size and Cold Starts: The Numbers Just Changed

This used to be the clearest argument against Prisma. It’s a lot less clear now.

Pre-Prisma 7, the picture was something like:

  • Drizzle: ~7 KB runtime, zero native deps
  • Kysely: ~9 KB runtime, zero native deps
  • Prisma 6: ~14 MB client + a Rust binary engine that pushed total deployment weight past 50 MB on serverless

Post-Prisma 7, Prisma’s own benchmarks claim the client is down to roughly 1.6 MB with cold starts about 9x faster than v6 and queries up to 3.4x faster on common workloads. That’s a real, defensible improvement, not marketing fluff. The architectural reason is honest too: removing the cross-language serialization between the JS runtime and the Rust process eliminated a measurable bottleneck.

But “Prisma is much smaller now” doesn’t mean “Prisma is the same as Drizzle now.” 1.6 MB is still roughly 200x larger than a 7 KB runtime. On Cloudflare Workers, where the per-script CPU and memory budget is tight and your bundle competes with everything else you’re shipping, that gap matters. On AWS Lambda with a 250 MB unzipped limit, it doesn’t matter at all.

If you’re deploying to a long-running Node.js server on Fly or ECS, none of this matters. Pick whatever you like the API of best. If you’re shipping to Workers or Vercel Edge with a strict bundle target, Drizzle and Kysely are still meaningfully lighter, and Drizzle’s API is closer to what most people actually want.

The Edge Runtime Story (And the Accelerate Tax)

Drizzle’s real moat for the last two years has been edge. It speaks raw fetch to HTTP-backed databases like Neon’s serverless driver, Turso/libSQL, Cloudflare D1, and PlanetScale’s Boost client. No Node APIs, no native modules, no proxy required. You just import it and go.

Kysely’s edge story is similar — pure JS, no native deps — but you need to bring your own driver. The official kysely core is dialect-agnostic; you wire it to Neon’s serverless driver, Postgres.js, or D1’s binding manually. Slightly more setup, but it works.

Prisma’s edge story is genuinely better post-v7. The client now runs natively on Workers, Bun, Deno Deploy, and Vercel Edge without Accelerate as a hard prerequisite. That’s new. For Postgres specifically, you can connect through Neon’s serverless driver or use Prisma’s own driver adapters. Connection pooling at the edge is still a problem you have to think about, but that’s true for everyone — you either go HTTP-driver-based, run a PgBouncer-style proxy, or pay for Accelerate.

About Accelerate: it’s still a real thing, still useful, and still a pricing surprise if you weren’t expecting it. The free tier covers small projects, but for production traffic the paid plans start adding up — and you’re paying for query caching plus connection pooling that you’d otherwise solve with PgBouncer or a serverless driver. With Drizzle and Kysely, you skip that line item by definition, because there’s no managed proxy to begin with. You still have to solve pooling, but you’re solving it with infra you control.

The honest comparison: Prisma 7 closed the “you literally can’t run on Workers” gap. It did not close the “your bill quietly grows” gap.

How Queries Actually Feel

This is the part you’ll live with every day, so it deserves more than a one-liner.

Prisma’s relational query API is genuinely lovely for the 80% case. Fetching a user with their last 10 published posts and the author of each post is roughly:

await prisma.user.findUnique({
  where: { id },
  include: {
    posts: {
      where: { published: true },
      take: 10,
      orderBy: { createdAt: "desc" },
      include: { author: true },
    },
  },
});

It’s readable, it’s fully typed, and the generated SQL is reasonable. Where Prisma starts to bite is the long tail: window functions, recursive CTEs, lateral joins, complex aggregations, and anything involving multiple JSON columns. You either drop into $queryRaw (losing type safety unless you template it carefully) or contort the query API into something nobody wants to maintain. The honest answer for those cases has always been “use raw SQL,” but if 30% of your queries are in that bucket, you’re fighting your tool half the time.

Drizzle’s relational queries API gives you most of the same ergonomics:

await db.query.users.findFirst({
  where: eq(users.id, id),
  with: {
    posts: {
      where: eq(posts.published, true),
      limit: 10,
      orderBy: desc(posts.createdAt),
      with: { author: true },
    },
  },
});

Looks similar. The real difference is what happens when you need to drop down. Drizzle’s core query builder is just SQL with types — db.select().from(users).leftJoin(...).where(...) reads like SQL because it is SQL. There’s no impedance mismatch between the two layers. You can mix them in the same file without it feeling weird.

Kysely makes you write the SQL yourself, but it makes the SQL pleasant:

const user = await db
  .selectFrom("users")
  .where("id", "=", id)
  .selectAll()
  .executeTakeFirst();
 
const posts = await db
  .selectFrom("posts")
  .innerJoin("users", "users.id", "posts.author_id")
  .where("posts.author_id", "=", id)
  .where("posts.published", "=", true)
  .orderBy("posts.created_at", "desc")
  .limit(10)
  .selectAll(["posts", "users"])
  .execute();

Two queries instead of one nested object. More code. But also: I know exactly what SQL is going to run, I can read the EXPLAIN, and when the query is slow I can fix it without arguing with an abstraction.

For complex analytics or reporting workloads, Kysely is the option I reach for. For a typical CRUD app, the extra typing is friction without payoff.

Migrations and Schema Management

Prisma’s migration tool is the best of the three by a comfortable margin. prisma migrate dev writes the SQL diff for you, runs it, regenerates the client, and you keep moving. prisma db push lets you iterate on the schema without writing migrations during early development. The migration history is a clean folder of timestamped SQL files. It mostly Just Works.

Drizzle Kit got a lot better in 2025 and is now in roughly the same league. drizzle-kit generate produces SQL diffs from your TypeScript schema. The diffs are sometimes less polished than Prisma’s — I’ve seen it suggest a destructive change when a smarter rename detection would have caught it — but you can hand-edit any migration before applying it, and that escape hatch matters.

Kysely makes no claim to do migrations for you. You write SQL files (or a small TypeScript migrator using Kysely’s own query builder), and you run them with kysely-migrator or whatever you prefer. This is genuinely more work, but it’s the same workflow you’d use for any well-run database project. If you have DBAs reviewing schema changes, this is what they want anyway.

For a new project where you want velocity, Prisma’s migration story is the strongest single feature in this comparison. Don’t underweight it.

Type Safety Past the Happy Path

All three give you typed query results. The interesting question is what happens when you stop writing toy queries.

Drizzle’s types track select shape precisely — pick three columns and you get back an object with three keys, not a partial of the full row. Joins compose. Discriminated unions on JSON columns work if you push the types yourself. Subqueries preserve type information through the chain. The one place I’ve seen it stumble is deeply nested relational queries with optional joins, where the inferred types occasionally widen in ways you wouldn’t expect.

Prisma’s types are strong on the relational query API but get fuzzier on $queryRaw even with the tagged-template helper. The new TypeScript-based engine in v7 tightened a few of the rough edges (notably JSON column types), but if you’re heavy on raw SQL, you’re going to be writing manual interfaces.

Kysely’s types are arguably the strictest of the three because the entire library is built around expressing SQL through the type system. The downside is that error messages can be enormous when you typo a column name in a complex join — the kind of expected "id" | "name" | ... 47 more, got "i_d" wall of text that takes a minute to parse. Trade-off: you catch the bug at compile time, but the diagnostic UX is rough.

What Migrating Actually Costs

The most useful framing I’ve found: migrating between any two of these is a project, not an afternoon.

Prisma → Drizzle on a moderate-sized app (20-50 models, 200-500 query call sites) is realistically a 1-3 week effort for one engineer. The schema translation is largely mechanical and there are codemods that get you 80% of the way. The query rewrites are where the time goes — every include becomes a with, every where operator changes shape ({ gt: 5 }gt(col, 5)), and every place you used Prisma’s automatic _count aggregations needs a manual subquery. Run both ORMs in parallel during cutover; don’t try a big-bang switch.

Prisma → Kysely is bigger. You’re not just changing API shape, you’re changing paradigm. Plan on 3-6 weeks for the same app, plus the time to set up your own migration tooling and codegen. Most teams that go this route are doing it because Prisma’s abstractions actively got in their way, not because of bundle size.

Drizzle → Kysely is the easiest transition because the mental models are close. Drizzle’s core query builder is already SQL-shaped; Kysely just removes the relations layer and makes the SQL more explicit.

The migration nobody talks about: Prisma 6 → Prisma 7. If you’re already on Prisma, the upgrade to v7 is more involved than a typical major version because the client internals changed substantially. Most apps will still be a few hours of work plus a careful regression pass on edge runtime deployments. Not a real reason to switch ORMs, but plan for the upgrade window.

Ecosystem and Adapters

Quick take, because this matters more than people admit:

  • Neon: First-class support across all three. Drizzle and Kysely both work well with Neon’s serverless HTTP driver for edge use.
  • PlanetScale: Drizzle is now first-class (acquisition synergy). Prisma works fine. Kysely has community adapters that are solid.
  • Supabase: Drizzle has the smoothest integration; Supabase’s own starter templates ship with it. Prisma works but you’re not using Supabase’s RLS as elegantly. Kysely works; you write the SQL.
  • Turso / libSQL: Drizzle is the canonical pick. Kysely has a dialect. Prisma supports it via driver adapters now in v7.
  • Cloudflare D1: Drizzle is the obvious choice. Kysely works. Prisma can work but it’s not where the docs and examples are.
  • Standard Postgres on RDS / Cloud SQL: Anything works. Pick by ergonomics.

If your hosted Postgres choice is going to change, Drizzle gives you the most flexibility to follow it. If you’re locked to RDS for compliance reasons, this dimension doesn’t matter.

Where I’d Land in 2026

For a new SaaS shipping to a long-running Node server on Postgres, with a team that values velocity over SQL purity: Prisma 7. The migration story is the best, the v7 improvements are real, and you’ll move fast. Budget for Accelerate or run your own pooler.

For anything edge-deployed — Workers, Vercel Edge, Bun on Fly, Deno Deploy — and especially anything on D1, Turso, or Neon: Drizzle. The bundle size argument still holds at the edge, the API is good enough that you won’t miss Prisma’s relations, and the PlanetScale acquisition means it’s not going anywhere.

For a backend team that’s strong in SQL, has DBAs in the review loop, or is building a system where 30%+ of queries are non-trivial (analytics, reporting, search ranking, multi-tenant queries with complex RLS): Kysely. You’ll write more code and get more control, and you won’t be fighting the tool when you need to ship a hairy query.

The mistake I see most often: teams pick Prisma because it’s the safe, well-known choice, then spend three months working around its abstractions on a workload that was always going to be SQL-heavy. The “boring” choice isn’t always the cheapest one. Look at what your queries actually look like before you commit.

If you’re starting fresh this week, my honest suggestion: spend an afternoon writing the same five real queries from your domain in all three. Not toy CRUD — the actual ones, including the one you’re already dreading. The one that wins isn’t always the one you expected.

Sources