better-drizzle

Migrating from raw Drizzle

A practical path from hand-written Drizzle query glue to better-drizzle delegates.

The easiest migration is incremental.

1. Keep your existing schema

Do not rewrite tables. better-drizzle reads tables and relations from the Drizzle instance, so declare relations with Drizzle ORM 1.x defineRelations(schema, ...) and pass them to drizzle(). With no relations, use defineRelations(schema).

2. Wrap the existing Drizzle client once

import { better } from 'better-drizzle';
import { drizzle } from 'drizzle-orm/node-postgres'; // Or your driver
import { relations } from './relations';

const db = drizzle({ client: connection, relations });
export const client = better(db);
import { better } from 'better-drizzle';
import { drizzle } from 'drizzle-orm/mysql2'; // Or your driver
import { relations } from './relations';

const db = drizzle({ client: connection, relations });
export const client = better(db);
import { better } from 'better-drizzle';
import { drizzle } from 'drizzle-orm/bun-sqlite'; // Or your driver
import { relations } from './relations';

const db = drizzle({ client: connection, relations });
export const client = better(db);
import { better } from 'better-drizzle';
import { drizzle } from 'drizzle-orm/node-postgres'; // Or your driver
import { relations } from './relations';

const db = drizzle({ client: connection, relations });
export const client = better(db);
import { better } from 'better-drizzle';
import { drizzle } from 'drizzle-orm/mysql2'; // Or your driver
import { relations } from './relations';

const db = drizzle({ client: connection, relations });
export const client = better(db);
import { better } from 'better-drizzle';
import { drizzle } from 'drizzle-orm/bun-sqlite'; // Or your driver
import { relations } from './relations';

const db = drizzle({ client: connection, relations });
export const client = better(db);

3. Move repetitive read paths first

Good first candidates:

  • point lookups
  • list pages with include
  • counts and existence checks
  • pagination endpoints
  • nested relation filters

These usually deliver the highest reduction in repeated service-layer code.

4. Replace branchy write glue with direct writes

const maybeCreated = await client.users.create({
	data: {
		email: 'alice@example.com',
		name: 'Alice',
	},
	skipDuplicates: ['email'],
});

And for bulk sync paths:

await client.users.upsertMany({
	data,
	target: ['email'],
	update: ['name', 'active'],
});

5. Move cross-cutting behavior into plugins or hooks

If you currently repeat logic like:

  • timestamps
  • soft delete visibility
  • audit metadata
  • trace IDs
  • authorization checks

those are usually better expressed once through plugins or hooks.

Raw Drizzle vs better-drizzle

Keep raw Drizzle when:

  • the query is truly custom SQL
  • you need a database-specific function or reporting query
  • the delegate shape would be more awkward than the SQL

Use better-drizzle when:

  • you're rebuilding CRUD and query glue by hand
  • you want predictable return shapes
  • you want typed relation filters and includes without repeating join code

Migration strategy that works well

  1. wrap the client
  2. migrate one service or endpoint at a time
  3. keep raw SQL where it is the clearest tool
  4. introduce plugins only after the base delegate API is in use

On this page