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 where filters across relations

better-drizzle packages those patterns into a repository-style API — without replacing Drizzle itself.

What you get

How the layers fit

  1. You define tables and relations with Drizzle.
  2. You create a normal Drizzle client with your driver of choice.
  3. You wrap it once with better(db, { schema }).
  4. You call typed delegates from your services — and drop down to raw SQL whenever it reads better.

Next steps

On this page