better-drizzle

better-drizzle vs Prisma

A Prisma-style API on top of Drizzle. How the calls map, what is different, and what Prisma has that better-drizzle does not.

If you know Prisma Client, most of better-drizzle will look familiar: findMany, where, include, select, upsert, _count. The difference is underneath. better-drizzle reads your Drizzle schema, compiles every call to a Drizzle query, and runs it on the driver you chose.

Short answer

Choose better-drizzle when you want Prisma-like ergonomics but prefer Drizzle's model: the schema is TypeScript, there is no generate step, and raw SQL stays a first-class tool on the same connection.

Choose Prisma when you want its full toolchain from one vendor: its schema language, migrations, Studio, and the API surface listed under what Prisma has.

How the calls map

Prismabetter-drizzleNotes
prisma.user.findUnique({ where })client.users.findUnique({ where })where must pin a primary or unique key
findUniqueOrThrow, findFirstOrThrowfindUnique(...).throw(), findFirst(...).throw().throw(factory) lets you throw your own error
findMany({ where, orderBy, skip, take })findMany({ where, orderBy, skip, take })negative take reverses the order
include / selectinclude / selectmutually exclusive at each level, nested at any depth
include: { _count: { select: { posts: true } } }the sameone correlated subquery, no extra round trip
orderBy: { posts: { _count: 'desc' } }the samerelation sorts are supported
equals, not, in, notIn, lt, lte, gt, gtethe same
contains, startsWith, endsWith, mode: 'insensitive'the sameinsensitive mode works on PostgreSQL, MySQL, and SQLite
AND, OR, NOTthe same
some, every, none, is, isNotthe same
count({ where })count({ where })plus exists({ where })
pagination with skip / takepaginate({ page, perPage }), cursor({ after, limit })both return { data, pagination } with totals or cursors
create({ data: { author: { connect } } })the sameconnect on create; connect, disconnect, set on update
createMany({ data, skipDuplicates })createMany({ data, skipDuplicates })skipDuplicates also takes a column list
update, updateMany, delete, deleteManythe same namesupdate / delete return null when nothing matches instead of throwing
upsert({ where, create, update })the samenative ON CONFLICT when the where allows it
{ increment: 1 }, { decrement }, { multiply }, { divide }the sameplus { toggle: true } for booleans
$transaction(async (tx) => ...)client.transaction(async (tx) => ...)nested calls become savepoints; optional retries
$queryRaw / $executeRaw$raw / $executeRawtagged templates, values bound as parameters
$queryRawUnsafe$rawUnsafedisabled unless raw.allowUnsafe is set
$extends (query, model, client)plugins, hooks, extends()
Prisma error codes (P2002, P2025)BetterDrizzleError and isUniqueViolation(error)see errors

The same query in both:

Prisma
const authors = await prisma.user.findMany({
	where: { active: true, posts: { some: { published: true } } },
	include: {
		posts: { where: { published: true }, orderBy: { id: 'desc' }, take: 3 },
		_count: { select: { posts: true } },
	},
	take: 10,
});
better-drizzle
const authors = await client.users.findMany({
	where: { active: true, posts: { some: { published: true } } },
	include: {
		posts: { where: { published: true }, orderBy: { id: 'desc' }, take: 3 },
		_count: { select: { posts: true } },
	},
	take: 10,
});

Model names come from your Drizzle schema keys (users, posts), not from Prisma model names (user, post).

What is different

  • Schema: Drizzle tables and defineRelations in TypeScript. There is no separate schema language and no generate step; types update as you edit the schema.
  • SQL-first: every read has .explain(), filters accept Drizzle sql fragments, and raw Drizzle runs on the same connection and inside the same transaction.
  • Drivers: the Drizzle driver you already use for PostgreSQL, MySQL, or SQLite. better-drizzle wraps the client and does not open its own connection.
  • Bulk writes: upsertMany (one native upsert per batch) and updateEach (one UPDATE ... CASE for rows with different values).
  • Row locks: lock: { mode: 'update' } on reads, on PostgreSQL and MySQL.
  • Plugins in the package: soft delete, timestamps, tenant isolation, read cache, UUID ids, Zod, and guardrails.
  • Not-found writes: update and delete return null; add .throw() to get an error.

What Prisma has that better-drizzle does not

  • Migrations: better-drizzle has none. Use drizzle-kit (generate, migrate, push) for migrations, and drizzle-kit studio to browse data.
  • Nested creates: relation writes are connect, disconnect, and set on existing rows. There is no nested create or connectOrCreate; create the related row first, inside a transaction.
  • Aggregates: no aggregate, groupBy, _sum, or _avg. Use the Drizzle query builder for those.
  • distinct and omit: not supported; use select to choose fields.
  • Batch transactions: $transaction([...]) with an array has no equivalent; use the callback form.
  • Other databases: no MongoDB, SQL Server, or CockroachDB support. better-drizzle supports PostgreSQL, MySQL, and SQLite.

See limitations for everything better-drizzle leaves to raw Drizzle.

Next steps

On this page