better-drizzle
Reference

Client

better() - options, client-level methods, and the values exported from better-drizzle.

relations.ts
import { defineRelations } from 'drizzle-orm';
import * as schema from './schema';

export const relations = defineRelations(schema, (r) => ({
	users: { posts: r.many.posts() },
	posts: { author: r.one.users({ from: r.posts.authorId, to: r.users.id }) },
}));
db.ts
import { better } from 'better-drizzle';
import { drizzle } from 'drizzle-orm/node-postgres';
import { relations } from './relations';

const db = drizzle({ connection: process.env.DATABASE_URL!, relations });

export const client = better(db, options); // options are optional: better(db)

better(db, options?) wraps a Drizzle database instance and returns a fully typed client. Tables and relations are read from the Drizzle instance (db._.relations), so it must be created with relations; use defineRelations(schema) without a callback when there are no relations. A Drizzle instance without tables makes better() throw No tables found on the Drizzle instance. Pass your relations to drizzle(), e.g. drizzle({ client, relations: defineRelations(schema) }).

The client's type parameter is the relations config: BetterDrizzleClient<typeof relations>.

Options

Prop

Type

Many-to-many relations are declared in defineRelations with .through(); see relations.

raw options

Prop

Type

When raw.enabled is false, every raw method throws a BetterDrizzleError with code RAW_DISABLED. Calling $rawUnsafe() without raw.allowUnsafe: true throws RAW_UNSAFE_DISABLED. With raw.requireComment, a call without options.comment throws RAW_COMMENT_REQUIRED. See error codes.

Client methods

Beyond the per-table delegates, the client exposes:

MethodDescription
client.<table>the model delegate for each table
repository(name)resolve a delegate by schema key or DB table name
extends(objectOrFactory)attach app-specific helpers or values to the client
transaction(fn, options?)run fn in a transaction
$raw(...)run a safe raw read, returns rows
$executeRaw(...)run a safe raw write, returns { rowsAffected }
$rawUnsafe(query, params?, options?)run a raw string query (requires raw.allowUnsafe)
$withContext(meta)clone the client with default metadata merged into every operation

repository(name) accepts either the TypeScript schema key or the underlying database table name:

const usersByKey = client.repository('users');
const usersByDbName = client.repository('app_users');

extends(...) accepts either a plain object or a callback that receives the bound client:

const extended = client.extends((client) => ({
	findById(id: number) {
		return client.users.findFirst({ where: { id } });
	},
}));

See the full guide at Client extensions.

$rawUnsafe

$rawUnsafe(query, params?, options?) takes a plain SQL string. Use ? placeholders for values: each one is replaced by the matching entry of params as a bound parameter, in order. It returns rows like $raw.

const client = better(db, { raw: { allowUnsafe: true } });

const rows = await client.$rawUnsafe(
	'select id, name from users where id = ? and active = ?',
	[1, true],
	{ name: 'admin-user-lookup' },
);

When params is non-empty and the number of ? placeholders differs from params.length, the call throws RAW_UNSAFE_PLACEHOLDER_MISMATCH (status 400) with details: { params, placeholders }. Without params, the string runs as-is.

Transaction clients

The client passed to a transaction(...) callback also has rollback(reason?), afterCommit(cb), and afterRollback(cb). See transactions. The root client also exposes afterCommit / afterRollback, but outside an active transaction they throw AFTER_COMMIT_OUTSIDE_TRANSACTION / AFTER_ROLLBACK_OUTSIDE_TRANSACTION.

Exports

import {
	better,
	definePlugin,
	version,
	// error helpers
	isDatabaseError,
	getDatabaseErrorInfo,
	isUniqueViolation,
	isForeignKeyViolation,
	isNotNullViolation,
	isCheckViolation,
	// errors
	BetterDrizzleError,
	BetterDrizzleErrorCode,
	BetterDrizzleTransactionRollbackError,
} from 'better-drizzle';

The library also exports its full type surface (BetterDrizzleClient, BetterDrizzleModelDelegate, QueryArgs, WhereInput, Plugin, hook context types, and more) for typing your own helpers.

The root and individual plugin entrypoints export the package version. Since they ship in one package, these are always the same value. The better-drizzle/plugins authoring entrypoint exports definePlugin only:

import { version } from 'better-drizzle';
import { version as softDeleteVersion } from 'better-drizzle/soft-delete';

version === softDeleteVersion; // true

On this page