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
- wrap the client
- migrate one service or endpoint at a time
- keep raw SQL where it is the clearest tool
- introduce plugins only after the base delegate API is in use