Introduction
Minimal, type-safe repository helpers for Drizzle ORM — keep the type-safety, drop the repetitive query glue.
better-drizzle wraps an existing Drizzle client and gives every table a small, consistent API for reads, writes, pagination, nested filters, relation loading, and optional hooks.
It is not a new ORM, and it does not try to hide Drizzle. You still define your schema, choose your driver, and reach for raw SQL whenever you want. better-drizzle just removes the query glue you would otherwise rewrite in every service.
import { better } from 'better-drizzle';
import { drizzle } from 'drizzle-orm/bun-sqlite';
const db = drizzle(sqlite, { schema });
const client = better(db, { schema });
await client.users.upsertMany({
data: [
{ email: 'alice@example.com', name: 'Alice', active: true },
{ email: 'bob@example.com', name: 'Bob', active: false },
],
target: ['email'],
update: ['name', 'active'],
});
const posts = await client.posts.findMany({
where: {
published: true,
author: { is: { active: true } },
},
select: {
id: true,
title: true,
author: { select: { id: true, name: true } },
},
orderBy: [{ id: 'desc' }],
take: 20,
});Type-safety, all the way down
Every delegate is generated from your Drizzle schema. Filters, projections,
relation includes, and return payloads are all inferred — posts above is
typed as the post row plus its author, no casts required.
Why it exists
Drizzle is excellent when you want explicit, SQL-first control. It gets repetitive when every service re-writes the same patterns:
- point lookups by id or unique field
- relation includes and projections
- pagination payloads with
count/hasNext/hasPrevious - existence checks and counts
- consistent CRUD return shapes
- nested
wherefilters across relations
better-drizzle packages those patterns into a repository-style API — without replacing Drizzle itself.
What you get
A delegate per table
client.users, client.posts, … each with findMany, findFirst,
create, update, delete, paginate, count, exists, upsert,
and upsertMany.
Typed nested filters
Filter by related rows with some / every / none / is, fully typed
against your relations.
One pagination shape
Offset and cursor pagination return the same { data, pagination } shape.
First-class plugins
Timestamps, soft delete, and your own — with transforms, hooks, and typed operation args.
Hooks & transactions
Lifecycle hooks for cross-cutting concerns; transactions with nested
savepoints, retries, and afterCommit.
Close to the metal
Fast paths for common reads and writes. Reads land within 0–18% of raw Drizzle — and use less memory.
How the layers fit
- You define tables and relations with Drizzle.
- You create a normal Drizzle client with your driver of choice.
- You wrap it once with
better(db, { schema }). - You call typed delegates from your services — and drop down to raw SQL whenever it reads better.