Drizzle
Drizzle ORM as a Suku service.
This follows the database pattern. The code is from examples/database and
is tested there against SQLite running in process.
bun add drizzle-ormSchema and connection
import { Database as Sqlite } from 'bun:sqlite';
import { service } from '@sukujs/core';
import { drizzle } from 'drizzle-orm/bun-sqlite';
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';
export const users = sqliteTable('users', {
id: integer('id').primaryKey({ autoIncrement: true }),
name: text('name').notNull(),
email: text('email').notNull().unique(),
});
export const Db = service('db', () => {
const sqlite = new Sqlite(env.DATABASE_FILE);
return {
orm: drizzle(sqlite, { schema: { users } }),
dispose: () => sqlite.close(),
};
});Opening a SQLite file is synchronous, so a plain service is enough. For PostgreSQL, create
the pool and pass it to drizzle-orm/node-postgres, with dispose: () => pool.end().
Repository
import type { ServiceOf } from '@sukujs/core';
const isUnique = (error: unknown) => {
const e = error as { code?: string; cause?: { code?: string } };
return (e.code ?? e.cause?.code)?.startsWith('SQLITE_CONSTRAINT') ?? false;
};
export class DrizzleUsers extends UsersRepository {
static inject = [Db];
constructor(private readonly db: ServiceOf<typeof Db>) {
super();
}
async list() {
return this.db.orm.select().from(users);
}
async create(input: NewUser) {
try {
const [row] = await this.db.orm.insert(users).values(input).returning();
return row!;
} catch (error) {
if (isUnique(error)) throw new DuplicateEmail(input.email);
throw error;
}
}
}Db is a classless service, so the repository lists it in static inject and takes its type
with ServiceOf. With PostgreSQL, check for error.code === '23505' instead.
Transactions
async createMany(inputs: NewUser[]) {
return this.db.orm.transaction((tx) =>
inputs.map((input) => tx.insert(users).values(input).returning().get()),
);
}SQLite transactions are synchronous
With bun:sqlite and better-sqlite3, the transaction callback cannot be async. With
PostgreSQL drivers it is, and you await each statement inside it.
Registering
export const UsersFeature = feature('users', {
routes: [usersRoutes],
services: [Db, singleton(UsersRepository, DrizzleUsers)],
onError: databaseErrors,
});Notes
- Migrations. Use
drizzle-kitas a deploy step. The example creates its table directly only because it runs in memory. - Typed results.
users.$inferSelectandusers.$inferInsertgive you row types, so the table definition stays the single source of truth.