See how this live Prisma vs Drizzle benchmark is set up, what each test covers, and how to read the numbers
This is a live benchmark of Prisma 7.1.0 and Drizzle ORM 0.45.0 hitting the same Neon PostgreSQL database via Next.js server actions. Prisma uses the PrismaNeon adapter; Drizzle uses neon-serverless with the same DATABASE_URL.
Each run executes the Prisma flow and then the Drizzle flow with identical inputs. Timings capture total execution time (network + Postgres + ORM work) using performance.now() on the server and are rounded to two decimals.
// Prisma CRUD Example
const user = await prisma.user.create({
data: { email: "user@example.com", name: "John" }
});
const found = await prisma.user.findUnique({
where: { id: user.id }
});
await prisma.user.update({
where: { id: user.id },
data: { name: "Jane" }
});
await prisma.user.delete({
where: { id: user.id }
});// Drizzle CRUD Example
const [user] = await db.insert(users)
.values({ email: "user@example.com", name: "John" })
.returning();
const found = await db.query.users.findFirst({
where: eq(users.id, user.id)
});
await db.update(users)
.set({ name: "Jane" })
.where(eq(users.id, user.id));
await db.delete(users)
.where(eq(users.id, user.id));// Prisma Join Example
const usersWithPosts = await prisma.user.findMany({
include: {
posts: {
include: {
comments: {
include: { author: true }
}
}
}
}
});// Drizzle Join Example
const usersWithPosts = await db.query.users.findMany({
with: {
posts: {
with: {
comments: {
with: { author: true }
}
}
}
}
});// Prisma Aggregation Example
const totalViews = await prisma.post.aggregate({
_sum: { viewCount: true }
});
const avgViews = await prisma.post.aggregate({
_avg: { viewCount: true }
});
const postsPerUser = await prisma.post.groupBy({
by: ["authorId"],
_count: { id: true }
});// Drizzle Aggregation Example
const totalViews = await db
.select({ sum: sum(posts.viewCount) })
.from(posts);
const avgViews = await db
.select({ avg: avg(posts.viewCount) })
.from(posts);
const postsPerUser = await db
.select({
authorId: posts.authorId,
count: count(posts.id)
})
.from(posts)
.groupBy(posts.authorId);// Prisma Subquery Example
const avgPosts = await prisma.post.groupBy({
by: ["authorId"],
_avg: { viewCount: true }
});
const usersAboveAvg = await prisma.user.findMany({
where: {
posts: {
some: {
viewCount: { gt: avgPosts[0]._avg.viewCount }
}
}
}
});// Drizzle Subquery Example
const avgPosts = db
.select({ avg: avg(posts.viewCount) })
.from(posts)
.as("avg_posts");
const usersAboveAvg = await db
.select()
.from(users)
.where(
gt(
sql`(SELECT AVG(view_count) FROM posts)`,
sql`(SELECT view_count FROM posts WHERE author_id = users.id)`
)
);// Prisma Transaction Example
await prisma.$transaction(async (tx) => {
const user = await tx.user.create({
data: { email: "user@example.com", name: "John" }
});
const post = await tx.post.create({
data: { title: "Post", authorId: user.id }
});
await tx.comment.create({
data: { content: "Comment", postId: post.id, authorId: user.id }
});
});// Drizzle Transaction Example
await db.transaction(async (tx) => {
const [user] = await tx.insert(users)
.values({ email: "user@example.com", name: "John" })
.returning();
const [post] = await tx.insert(posts)
.values({ title: "Post", authorId: user.id })
.returning();
await tx.insert(comments)
.values({ content: "Comment", postId: post.id, authorId: user.id });
});// Prisma Bulk Example
await prisma.user.createMany({
data: Array.from({ length: 100 }, (_, i) => ({
email: `user${i}@example.com`,
name: `User ${i}`
}))
});
await prisma.user.updateMany({
where: { email: { startsWith: "user" } },
data: { name: "Updated" }
});
await prisma.user.deleteMany({
where: { email: { startsWith: "user" } }
});// Drizzle Bulk Example
await db.insert(users).values(
Array.from({ length: 100 }, (_, i) => ({
email: `user${i}@example.com`,
name: `User ${i}`
}))
);
await db.update(users)
.set({ name: "Updated" })
.where(like(users.email, "user%"));
await db.delete(users)
.where(like(users.email, "user%"));Each click runs one end-to-end pass per ORM (no batching). Timers wrap the whole server action, so numbers include network, Postgres, and ORM overhead and may vary slightly run to run.
Both ORMs hit the same schema, dataset, region, and DATABASE_URL. Query shapes, filters, joins, groupings, and limits are mirrored as closely as the APIs allow.
Workloads mirror production-style patterns: relational joins across users/posts/comments/categories, aggregates with HAVING clauses, nested subqueries, multi-step transactions, and 100-row bulk writes.