Skip to content

Database ​

createDb(config, schema) opens a SQLite database through better-sqlite3 and hands you a Drizzle instance on db.drizzle. Queries are Drizzle's; the module adds what Drizzle leaves to you: running migrations, seeding, test-data factories, pagination metadata and closing the connection.

It is the module for apps on a SQL database, and it runs on Node only: better-sqlite3 is a native binding. The package's own apps keep their data in flatdb and call it directly, the way the quick start does; flatdb's find(filter, { sort, limit, skip }) and count are its pagination. Both packages db needs are optional peers: npm install drizzle-orm better-sqlite3.

Import ​

ts
import { createDb } from '@loewen-digital/fullstack/db'

Schema and queries ​

Define the schema with Drizzle's sqlite-core and pass it as the second argument; db.drizzle is then typed for it, including the relational query API.

ts
import { createDb } from '@loewen-digital/fullstack/db'
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'
import { eq } from 'drizzle-orm'

export const users = sqliteTable('users', {
  id: integer('id').primaryKey({ autoIncrement: true }),
  email: text('email').notNull().unique(),
  name: text('name').notNull(),
  active: integer('active', { mode: 'boolean' }).notNull().default(true),
  createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
})

export const db = createDb({ driver: 'sqlite', url: './app.db', migrations: './drizzle' }, { users })

export function findByEmail(email: string) {
  return db.drizzle.query.users.findFirst({ where: eq(users.email, email) })
}

export function activeUsers() {
  return db.drizzle.select().from(users).where(eq(users.active, true))
}

url is the file path, or ':memory:' for a throwaway database. Files run in WAL mode.

Migrations ​

migrate() runs the migrations Drizzle Kit generated into config.migrations (default ./drizzle); rollback() forgets the last applied migration in Drizzle's journal (it does not undo the SQL); migrationStatus() lists what is applied. The CLI exposes the same three as fullstack migrate, migrate:rollback and migrate:status.

ts
async function prepare() {
  await db.migrate() // or db.migrate('./other/folder')
  const status = await db.migrationStatus() // [{ name, applied: true, appliedAt }]
  return status.length
}

Seeds ​

seed(fn) runs a function against the instance and wraps a failure in Seed failed: ....

ts
async function seedUsers() {
  await db.seed(async (db) => {
    await db.drizzle.insert(users).values([
      { email: 'alice@example.com', name: 'Alice', createdAt: new Date() },
      { email: 'bob@example.com', name: 'Bob', createdAt: new Date() },
    ])
  })
}

Factories ​

factory(definition) binds a test-data factory to the instance: make builds an object, create builds and inserts it through your insert, createMany(n) repeats that.

ts
const userFactory = db.factory<typeof users.$inferInsert>({
  build: (overrides) => ({
    email: `user-${crypto.randomUUID()}@example.com`,
    name: 'Alice',
    createdAt: new Date(),
    ...overrides,
  }),
  insert: async (db, user) => {
    await db.drizzle.insert(users).values(user)
    return user
  },
})

async function seedTestUsers() {
  const bob = await userFactory.create({ name: 'Bob' })
  const more = await userFactory.createMany(5)
  return [bob, ...more]
}

Pagination ​

paginate(data, total, { page, perPage }) computes the metadata for a page you fetched with limit and offset; it runs no query itself.

ts
import { count } from 'drizzle-orm'
async function usersPage(page: number, perPage = 20) {
  const rows = await db.drizzle.select().from(users).limit(perPage).offset((page - 1) * perPage)
  const [row] = await db.drizzle.select({ total: count() }).from(users)
  return db.paginate(rows, row?.total ?? 0, { page, perPage }) // { data, total, page, perPage, lastPage }
}

paginateHelper is the same function without an instance.

Closing ​

close() closes the SQLite handle; call it when a script or test is done.

Drivers ​

DriverStatus
sqlitebetter-sqlite3, an optional peer dependency: npm install better-sqlite3. createDb throws with that hint when it is missing
postgres, mysql, d1Declared in the config type; createDb throws for them. Use Drizzle's own driver for these databases directly

Config options ​

createDb(config, schema?) reads these.

OptionTypeDefaultDescription
driver'sqlite'—Only sqlite is implemented
urlstring—File path, or ':memory:'
migrationsstring'./drizzle'Folder migrate() and the CLI read
schema (2nd argument)Drizzle tables{}Types db.drizzle and enables db.drizzle.query