Next.js & React

Drizzle vs Prisma in 2026: Benchmarks, Drizzle 1.0, Prisma 8 and Which to Choose

Drizzle vs Prisma, benchmarked on PostgreSQL 17 across Drizzle 0.45, Drizzle 1.0 RC, Prisma 7 and Prisma 8 RC: query speed, the OFFSET trap, cold starts, install size, migrations and which to choose.

Drizzle vs Prisma cover: a bench.ts panel with deep-page timings for Prisma 7, Drizzle 0.45, Drizzle 1.0, Prisma 8 RC and keyset pagination on PostgreSQL 17
On this page
  1. TL;DR: Drizzle or Prisma in 2026?
  2. What is the difference between Drizzle and Prisma?
  3. How do queries compare in Drizzle and Prisma?
  4. Is Drizzle faster than Prisma?
  5. Benchmark setup
  6. Results (median, milliseconds)
  7. Why are nested queries with OFFSET slow?
  8. How do I fix slow nested pagination?
  9. Which has a faster cold start, Drizzle or Prisma?
  10. How big is Drizzle vs Prisma in nodemodules?
  11. How do migrations compare?
  12. What changed in Drizzle 1.0?
  13. Should I use Prisma 8 or Prisma 7?
  14. Which should I choose for Next.js?
  15. Common Drizzle and Prisma mistakes
  16. Key takeaways

Drizzle vs Prisma comes down to how you want to write data access in TypeScript. Drizzle is a thin, SQL-shaped query builder: your schema is TypeScript and every query maps closely to the SQL it sends. Prisma is a higher-level ORM: you write a schema file and get a generated, model-based client. Both are type-safe, and both work well with PostgreSQL and Next.js.

In October 2026 both are in the middle of a major version. Drizzle 1.0 is a release candidate while 0.45.4 is still npm latest. Prisma 8 is a release candidate that npm latest already installs. So I benchmarked all five combinations against the same PostgreSQL 17 database. On simple queries the gap was a tenth of a millisecond. On one common query pattern it was 6-19x, and the cause was the shape of the SQL, not the ORM.

TL;DR: Drizzle or Prisma in 2026?#

Pick Drizzle if you think in SQL, deploy to serverless or edge, and want the smallest install and fastest cold start: 30 MB and about 35 ms bundled in my test. Pick Prisma 7 if you want the most guided workflow, a generated client and Prisma Migrate. Wait for GA before moving production to Drizzle 1.0 or Prisma 8.

DrizzlePrisma
Stable line0.45.4 (npm latest)7.10.1 (npm prev)
Next major1.0.0-rc.4 (npm rc)8.0.0-rc (npm latest)
Schema lives inTypeScript (pgTable)schema.prisma (7) or contract.prisma (8)
Code generationNoneprisma generate (7), prisma contract emit (8)
Query styleSQL builder + relational db.queryModel client (7), chained db.orm (8)
Migrationsdrizzle-kit generate / migrate / pushPrisma Migrate
Production install (my test)21-30 MB, 18 packages329 MB (7), 130 MB (8)
Bundled cold start (my test)~35 ms~100 ms (7), ~370 ms (8 RC)

What is the difference between Drizzle and Prisma?#

Drizzle defines tables in TypeScript and gives you a typed SQL builder, so db.select().from(users).where(...) reads like the SQL it sends, with no generate step. Prisma defines models in its own schema language, generates a client and hides SQL behind model methods. Drizzle is closer to the database; Prisma is closer to your domain model.

The same table in each:

ts
import { defineRelations } from "drizzle-orm";
import { boolean, index, integer, pgTable, serial, text, timestamp } from "drizzle-orm/pg-core";

export const users = pgTable("User", {
  id: serial().primaryKey(),
  email: text().notNull().unique(),
  name: text(),
  createdAt: timestamp({ precision: 3, mode: "date" }).notNull().defaultNow(),
});

export const posts = pgTable("Post", {
  id: serial().primaryKey(),
  title: text().notNull(),
  published: boolean().notNull().default(false),
  views: integer().notNull().default(0),
  authorId: integer().notNull().references(() => users.id, { onDelete: "cascade" }),
}, (t) => [index("Post_authorId_idx").on(t.authorId)]);

export const relations = defineRelations({ users, posts }, (r) => ({
  users: { posts: r.many.posts() },
  posts: { author: r.one.users({ from: r.posts.authorId, to: r.users.id }) },
}));
prisma
model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  createdAt DateTime @default(now())
  posts     Post[]
}

model Post {
  id        Int     @id @default(autoincrement())
  title     String
  published Boolean @default(false)
  views     Int     @default(0)
  authorId  Int
  author    User    @relation(fields: [authorId], references: [id], onDelete: Cascade)

  @@index([authorId])
}

How do queries compare in Drizzle and Prisma?#

All four clients can fetch a user with their posts in one call. Drizzle 1.0 uses object filters in db.query, Prisma 7 uses findMany with include, and Prisma 8 chains .include("posts") on a collection. The type-safety level is the same; the difference is whether the code looks like SQL or like method calls.

These are the exact queries from my benchmark, and all of them type-check under strict:

ts
// Drizzle 0.45 (RQB v1)
db.query.users.findMany({ with: { posts: true }, orderBy: (u, { asc }) => [asc(u.id)], offset, limit: 20 });

// Drizzle 1.0 (RQB v2)
db.query.users.findMany({ with: { posts: true }, orderBy: { id: "asc" }, offset, limit: 20 });

// Prisma 7
prisma.user.findMany({ include: { posts: true }, orderBy: { id: "asc" }, skip: offset, take: 20 });

// Prisma 8 RC
db.orm.public.User.include("posts").orderBy((u) => u.id.asc()).offset(offset).limit(20).all();

Drizzle also keeps its plain SQL builder (db.select().from(posts).innerJoin(...)) for anything the relational API does not cover. Prisma 7 falls back to $queryRaw or TypedSQL, and Prisma 8 has its own SQL builder. I covered Prisma 8's query API in detail in my Prisma 8 guide.

Is Drizzle faster than Prisma?#

For single-row reads and inserts, barely. In my test every client answered a primary-key lookup in 0.12-0.20 ms against local PostgreSQL, and over a network that gap disappears. The real difference was in nested pagination: with OFFSET, single-query JSON loading got 6-11x slower than Prisma 7's two-query default.

Benchmark setup#

  • PostgreSQL 17.11 on the same machine (8 vCPU Xeon, 15 GB RAM), Node.js 24.22.0, 1,000 users and 10,000 posts with an index on Post.authorId.
  • Each case: 200 warm-up calls, then 2,000 timed calls. Each client ran 3 times, with inserted rows deleted before each run. The table shows the median of the three per-run medians.
  • Versions: Prisma 7.10.1 with @prisma/adapter-pg, Drizzle 0.45.4, Drizzle 1.0.0-rc.4 (with and without jit: true), and Prisma 8 (@prisma/orm-postgres 8.0.0-rc.18).
  • "Deep page" means 20 users plus their posts at OFFSET 0 to 960.

Results (median, milliseconds)#

QueryPrisma 7.10.1Drizzle 0.45.4Drizzle 1.0 rc.4Drizzle 1.0 jitPrisma 8 rc.18
Find user by id0.1500.1290.1210.1360.195
First page, 20 users + posts0.7300.7190.5610.6171.185
Deep page, 20 users + posts0.7494.6505.1645.2878.060
Insert one post0.1680.1460.1390.1640.208
Process RSS after run289 MB251 MB295 MB296 MB404 MB

Two things stand out. Drizzle 1.0 was the fastest at nearly everything, but jit: true made no measurable difference here. Drizzle documents it as "just-in-time compiled row mappers," and mapping 20 rows is not where the time goes. And the deep-page row looks like a Prisma win, but it isn't one, as the next section shows.

Why are nested queries with OFFSET slow?#

Drizzle and Prisma 8 load relations in one SQL statement, using a JSON subquery for each parent row. PostgreSQL applies OFFSET last, so it builds the JSON for every skipped row and then throws it away. At OFFSET 960, EXPLAIN ANALYZE showed the posts subquery running 980 times to return 20 users.

Why OFFSET is slow with single-query relation loading: PostgreSQL builds JSON posts for every skipped row before applying OFFSET, while two-query loading and keyset pagination only touch 20 users

This is the SQL Drizzle 0.45 generated (Drizzle 1.0 is nearly identical):

sql
select "users"."id", "users"."email", "users"."name", "users"."createdAt", "users_posts"."data" as "posts"
from "User" "users"
left join lateral (
  select coalesce(json_agg(json_build_array(...)), '[]'::json) as "data"
  from "Post" "users_posts" where "users_posts"."authorId" = "users"."id"
) "users_posts" on true
order by "users"."id" asc limit $1 offset $2

The PostgreSQL docs warn about this directly: "The rows skipped by an OFFSET clause still have to be computed inside the server; therefore a large OFFSET might be inefficient" (PostgreSQL 17, LIMIT and OFFSET). Here, each skipped row includes a full posts subquery.

Prisma 7 avoided it only because it sent two queries: the 20 users, then SELECT ... FROM "Post" WHERE "authorId" IN (...). When I turned on Prisma 7's relationJoins preview and set relationLoadStrategy: "join", the deep page went from 0.79 ms to 14.8 ms, the slowest result in the whole test. Prisma's relation queries docs say join uses "a combination of LATERAL JOINs and JSON aggregation", which is the same shape.

How do I fix slow nested pagination?#

Use keyset (cursor) pagination instead of OFFSET: ask for "the 20 users after id X". PostgreSQL then seeks straight to the right index position and builds JSON for only 20 rows. In my test, Drizzle 1.0 deep pages dropped from 5.16 ms to 0.66 ms and Prisma 8 from 8.06 ms to 1.29 ms.

ts
// Drizzle 1.0
db.query.users.findMany({ with: { posts: true }, where: { id: { gt: afterId } }, orderBy: { id: "asc" }, limit: 20 });

// Prisma 8 RC
db.orm.public.User.where((u) => u.id.gt(afterId)).include("posts").orderBy((u) => u.id.asc()).limit(20).all();

// Prisma 7
prisma.user.findMany({ include: { posts: true }, cursor: { id: afterId }, skip: 1, take: 20, orderBy: { id: "asc" } });

Prisma's pagination guide recommends the same: use cursor-based pagination "when you care more about performance and consistency as the dataset grows." If you need page numbers, run the parent query first and load children with a second IN (...) query. That is exactly what Prisma 7's default does.

Which has a faster cold start, Drizzle or Prisma?#

Drizzle, once bundled. With esbuild, importing the client and running the first query took about 35 ms with Drizzle, about 100 ms with Prisma 7 and about 370 ms with Prisma 8 RC. Unbundled, Drizzle was slower than Prisma 7, because Node loaded 425-528 separate Drizzle module files from node_modules.

Median of 5 process startsPrisma 7.10.1Drizzle 0.45.4Drizzle 1.0 rc.4Prisma 8 rc.18
Unbundled: import + first query55 + 89 ms400 + 31 ms699 + 18 ms494 + 30 ms
esbuild bundle: import + first query21 + 78 ms17 + 19 ms16 + 18 ms342 + 26 ms
Bundle size5.0 MB448 KB476 KB1.8 MB

Next.js and most serverless builds bundle server code, so the bundled row is the one that usually applies. Prisma 7's import is fast, but its first query costs about 78 ms; the client ships its query compiler as base64-encoded WebAssembly, which is the likely cost of that first call. Prisma 8 RC's time goes into startup itself. A CPU profile showed the top functions inside arktype's @ark/schema and @ark/util, which Prisma 8 uses for runtime validation. That is RC behaviour and may change before GA.

How big is Drizzle vs Prisma in node_modules?#

Drizzle installed in 21 MB (0.45.4) or 30 MB (1.0 RC) with 18 packages, including pg. With npm ci --omit=dev, Prisma 7 came to 329 MB and 345 packages, and Prisma 8 RC to 130 MB and 118 packages. For Docker images and serverless size limits, that is the clearest difference between them.

Where the Prisma weight comes from, according to npm ls and du:

  • Prisma 7: @prisma/client (72 MB) lists prisma and typescript as optional peer dependencies. My project had both as devDependencies, so npm kept them in the production tree, along with the CLI, @prisma/studio-core (43 MB) and @prisma/dev.
  • Prisma 8 RC: @prisma/orm-postgres depends on @prisma/orm-toolchain, which brings typescript, prettier and rolldown into a production install.

If image size matters, bundle the server and copy only the output. My Next.js AI starter kit uses Drizzle partly for this reason.

How do migrations compare?#

Both generate SQL migration files from your schema. drizzle-kit diffs your TypeScript schema and writes plain SQL, and push can sync a dev database without a migration file. Prisma Migrate diffs schema.prisma, keeps history in prisma/migrations and manages a shadow database for migrate dev. Both let you edit the generated SQL before applying it.

Drizzle 1.0 changed the folder layout. npx drizzle-kit generate --name init created this:

text
drizzle/
└── 20261017144733_init/
    ├── migration.sql
    └── snapshot.json

There is no meta/_journal.json any more. The Drizzle v1 upgrade guide says this removes "potential Git conflicts with the journal file," and drizzle-kit generate now runs commutativity checks that flag migrations on two branches that change the same thing. Run npx drizzle-kit up once to convert an existing 0.x migrations folder.

Prisma Migrate is still the more guided tool: prisma migrate dev uses a shadow database to detect drift and offers to reset the dev database when history and schema disagree. One Prisma 7 change to know: seeding no longer runs automatically during migrate dev or migrate reset; you run npx prisma db seed yourself. See the Prisma Migrate docs.

What changed in Drizzle 1.0?#

Drizzle 1.0 removes the old relational query API (RQB v1) and replaces it with defineRelations() and RQB v2. Filters and ordering become plain objects. Validators move into the package as drizzle-orm/zod and similar, getTableColumns becomes getColumns, and drizzle-kit drops the migration journal. It also adds MSSQL and CockroachDB dialects.

From the official v0 to v1 changes page:

Area0.x1.0
Relationsrelations() per tableOne defineRelations() call
Filterswhere: (u, { eq }) => eq(u.id, 1)where: { id: 1 }
Casingdrizzle({ casing: "snake_case" })snakeCase.table(...)
Validationdrizzle-zod packagedrizzle-orm/zod
Migrationsmeta/_journal.jsonOne folder per migration
push / pullpublic schema only by defaultAll schemas by default
Introspection~10 sunder 1 s (Drizzle's figure)

The relations v1 to v2 guide covers the query migration step by step. As of October 17, 2026, npm latest is still 0.45.4 (published October 8) and rc is 1.0.0-rc.4 (June 27).

Should I use Prisma 8 or Prisma 7?#

For production today, Prisma 7. Prisma 8 is a rewrite with a new contract file, a new runtime package and a chained query API, and it is still a release candidate. In my test it was also the slowest to start and the heaviest per process. Prisma 8 is worth trying on a branch now; plan the switch after GA.

Note that npm install prisma already installs Prisma 8, so pin prisma@7 and @prisma/client@7 in existing projects. The full migration path, with the contract and the new client, is in my Prisma 8 guide.

Which should I choose for Next.js?#

Both work in Next.js 16 server components, route handlers and server actions. Choose Drizzle for Vercel functions, edge routes or small Docker images, where cold start and install size matter. Choose Prisma 7 when a team wants one schema file, generated types and Prisma Migrate's guardrails more than they want SQL control.

Decision chart for Drizzle vs Prisma in 2026: SQL-first, serverless and small images point to Drizzle; schema-first teams and guided migrations point to Prisma 7; Drizzle 1.0 and Prisma 8 wait for GA

Your situationPick
You think in SQL and want queries that map one-to-oneDrizzle
Serverless, edge or tight cold-start budgetDrizzle
Small Docker image or function size limitDrizzle
Team wants one schema file and a generated clientPrisma 7
Guided migrations with drift detection and a shadow DBPrisma 7
Existing Prisma 6/7 appStay on Prisma 7, pin it, test 8 on a branch
New app starting this monthDrizzle 0.45 or Prisma 7, and plan the move to 1.0 / 8
Paginating nested dataEither: use keyset pagination, not deep OFFSET

Auth libraries matter here too. Better Auth 1.7 lists both drizzle-orm (0.45.2+ or 1.0 RC) and Prisma (5 to 7) as supported peers, but not Prisma 8 yet.

Common Drizzle and Prisma mistakes#

  • Deep OFFSET with nested relations. Both ORMs build JSON for every skipped row. Use keyset pagination or a second query.
  • Turning on Prisma relationJoins everywhere. In my test it was slower for paginated includes. Measure per query.
  • Unpinned prisma installs. latest is Prisma 8 RC. Pin major versions in Prisma 7 projects.
  • Expecting jit: true to fix slow queries. It speeds up row mapping, not the SQL.
  • Running Drizzle unbundled on serverless. It loads hundreds of module files at startup. Bundle the server output.
  • Upgrading to Drizzle 1.0 without drizzle-kit up. Old migration folders must be converted first.

Key takeaways#

  • Drizzle is a SQL-first TypeScript query builder with no code generation; Prisma is a schema-first ORM with a generated client. Both are fully type-safe.
  • On simple queries, all five clients I tested answered within 0.12-0.21 ms of local PostgreSQL, so raw speed rarely decides the choice.
  • Single-query relation loading plus a deep OFFSET was 6-19x slower than two queries, whether it came from Drizzle, Prisma 8 or Prisma 7 with relationJoins. Keyset pagination fixed it.
  • Bundled, Drizzle started in about 35 ms, Prisma 7 in about 100 ms and Prisma 8 RC in about 370 ms.
  • Production installs were 21-30 MB for Drizzle, 329 MB for Prisma 7 and 130 MB for Prisma 8 RC.
  • Drizzle 1.0 and Prisma 8 are both release candidates on October 17, 2026; use Drizzle 0.45 or Prisma 7 in production and pin your versions.

Questions