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.

On this page
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.
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:
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 }) },
}));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:
// 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 withoutjit: true), and Prisma 8 (@prisma/orm-postgres8.0.0-rc.18). - "Deep page" means 20 users plus their posts at
OFFSET0 to 960.
Results (median, milliseconds)#
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.

This is the SQL Drizzle 0.45 generated (Drizzle 1.0 is nearly identical):
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 $2The 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.
// 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.
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) listsprismaandtypescriptas 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-postgresdepends on@prisma/orm-toolchain, which bringstypescript,prettierand 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:
drizzle/
└── 20261017144733_init/
├── migration.sql
└── snapshot.jsonThere 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:
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.

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
OFFSETwith nested relations. Both ORMs build JSON for every skipped row. Use keyset pagination or a second query. - Turning on Prisma
relationJoinseverywhere. In my test it was slower for paginated includes. Measure per query. - Unpinned
prismainstalls.latestis Prisma 8 RC. Pin major versions in Prisma 7 projects. - Expecting
jit: trueto 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
Share


