Skip to content

FedorenkoCodes/drizzle-orm-node-sqlite

Repository files navigation

drizzle-orm-node-sqlite

A tiny adapter that lets Drizzle ORM talk to Node’s built-in node:sqlite - no extra native modules. It works anywhere Node runs (Node/Electron main or preload). It’s not a browser driver.

Under the hood this uses Drizzle’s SQLite Proxy driver: Drizzle builds SQL + parameters and calls our executor; we execute with node:sqlite and return rows in the shape Drizzle expects. Drizzle’s proxy contract requires array-shaped rows for all/get/values - this adapter ensures that, including on older Node versions. (orm.drizzle.team)


Requirements

  • Node.js ≥ 22.5.0 (when node:sqlite was introduced; stability “Active development”). (nodejs.org)

  • Drizzle ORM ≥ 0.44.0 (tested up to 0.44.x).

Node 22 vs Node 24

  • Node 22 returns rows as objects.

  • Node 24+ can return rows as arrays via statement.setReturnArrays(true) or new DatabaseSync(path, { returnArrays: true }). This adapter works either way; if arrays are already returned, it forwards them unchanged. (nodejs.org)


Install

npm i drizzle-orm drizzle-orm-node-sqlite
# or: pnpm add / yarn add

TypeScript projects should use a modern resolver so TS respects package exports:

// tsconfig.json
{
  "compilerOptions": {
    "moduleResolution": "NodeNext",
    "types": ["node"]
  }
}

Quick start

// db.ts
import { drizzle } from 'drizzle-orm-node-sqlite'
import * as schema from './schema' // your drizzle-orm/sqlite-core tables

// If migrating from libsql, normalize "file:*.db" to a file path:
const DB_PATH = (process.env.DB_FILE_NAME ?? 'local.db').replace(/^file:/, '')

export const db = drizzle({
  client: DB_PATH,  // or ':memory:' or an existing new DatabaseSync(DB_PATH, { ...options })
  schema,           // required if you want db.query.<table>.*
  readBigInts: true // maps INTEGERs to JS bigint (see BigInt section)
})

Why pass schema? Because Drizzle only creates the db.query.<table> helpers when you initialize with your schema. (orm.drizzle.team)


Example

import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'
import { eq } from 'drizzle-orm'
import { db } from './db'

export const users = sqliteTable('users', {
  id: integer().primaryKey({ autoIncrement: true }),
  username: text().notNull(),
})

await db.execute(`
  create table if not exists users(
    id integer primary key autoincrement,
    username text not null
  )
`)

await db.insert(users).values({ username: 'ada' })

const first = await db.query.users.findFirst({
  where: (t, { eq }) => eq(t.username, 'ada')
})

console.log(first)

Options you can pass

These map to node:sqlite statement behaviors and Drizzle proxy ergonomics:

drizzle({
  client: 'app.db',         // string path or a DatabaseSync instance
  schema,                   // enables db.query.*
  logger: true,             // Drizzle logger passthrough
  readBigInts: true,        // StatementSync#setReadBigInts(true)
  allowBareNamedParameters: true,
  allowUnknownNamedParameters: false,
})

readBigInts makes INTEGER reads come back as bigint instead of number. (Writes accept either.) (nodejs.org)


Node 22 vs Node 24 behavior

Node 22 (v22.x)

  • statement.setReturnArrays() is not available; statements return objects.

  • This adapter converts objects → arrays (in DB column order) to satisfy Drizzle’s proxy contract. (orm.drizzle.team)

Node 24 (v24.x)

  • You can opt into array rows and skip conversion:

    import { DatabaseSync } from 'node:sqlite'
    const sqlite = new DatabaseSync('app.db', {
      returnArrays: true, // rows as arrays
      timeout: 2000       // busy timeout in ms (optional)
    })
    const db = drizzle({ client: sqlite, schema })

    setReturnArrays(enabled) on StatementSync and the returnArrays / timeout database options were added in v24.x. DatabaseSync APIs execute synchronously. (nodejs.org)


BigInt & JSON (tRPC, RPCs, HTTP)

If you enable readBigInts: true, you’ll get JS bigint values for large integers. Plain JSON.stringify cannot serialize bigint, so add a data transformer (e.g. SuperJSON) to your API layer.

tRPC (server):

import { initTRPC } from '@trpc/server'
import superjson from 'superjson'
export const t = initTRPC.create({ transformer: superjson })

tRPC (client):

import { createTRPCClient, httpLink } from '@trpc/client'
import superjson from 'superjson'
export const client = createTRPCClient({ links: [httpLink({ url, transformer: superjson })] })

tRPC docs: “Data transformers” (SuperJSON & others). (trpc.io)

If you don’t need exact precision, set readBigInts: false and you’ll get JS numbers instead. (nodejs.org)


Migrations (Drizzle Kit)

Keep using the SQLite dialect and point Drizzle Kit at the same file path:

// drizzle.config.ts
import { defineConfig } from 'drizzle-kit'
export default defineConfig({
  out: './drizzle',
  schema: './src/schema.ts',
  dialect: 'sqlite',
  dbCredentials: { url: './app.db' } // or your path
})

Drizzle Kit reads dialect plus dbCredentials to run migrations. (orm.drizzle.team)


Transactions & batch

Drizzle’s proxy driver supports db.batch([...]) and regular transactions (BEGIN/COMMIT/ROLLBACK) - this adapter just executes whatever SQL Drizzle emits. It returns rows as arrays as required by the proxy contract. (orm.drizzle.team)


Development

To contribute, first set up the development environment:

# Clone the repository
git clone https://github.com/your-repo/drizzle-orm-node-sqlite.git
cd drizzle-orm-node-sqlite

# Install dependencies
yarn install

The following scripts are available:

  • yarn build: Compiles TypeScript source to JavaScript in the dist/ directory.

  • yarn test: Builds the project and runs the integration tests.

  • yarn lint: Lints the codebase for style and consistency issues.

  • yarn lint:fix: Automatically fixes fixable linting issues.


Performance & concurrency tips

  • Synchronous API: DatabaseSync APIs are synchronous; long-running queries can block the event loop. For heavy work, consider moving DB operations to a Worker Thread or a simple job queue. (nodejs.org)

  • WAL mode: For multi-reader/mixed workloads, enabling Write-Ahead Logging often improves concurrency (readers don’t block writers and vice-versa). Do this once at startup:

    sqlite.exec(`PRAGMA journal_mode = WAL;`)

    (See SQLite’s WAL docs for trade-offs.) (www3.sqlite.org)

  • Busy timeout (Node 24+): You can set timeout in new DatabaseSync(path, { timeout: 2000 }) to reduce SQLITE_BUSY errors under contention. (nodejs.org)


Electron notes

Use this in the main process or a preload (with Node APIs exposed). If your renderer is sandboxed (no Node APIs), call into the main process via IPC. node:sqlite is a Node builtin (available via the node: scheme), not a web API. (nodejs.org)


FAQ

Q: Is this browser-compatible?
A: No. node:sqlite runs in Node. For browsers, use an HTTP proxy driver or a WASM SQLite build. Drizzle’s proxy docs cover how to return array rows from a HTTP service. (orm.drizzle.team)

Q: Do I have to pass schema?
A: Only if you want the db.query.<table> helpers. Without it, use the regular query builder db.select().from(t) etc. (orm.drizzle.team)

Q: What about COUNT(*) returning bigint?
A: With readBigInts: true, counts may be bigint. Serialize via a transformer (e.g., SuperJSON), or cast to TEXT/REAL in SQL if you must return plain JSON. (trpc.io)


How it works (brief)

  • Drizzle’s SQLite Proxy driver calls a function with (sql, params, method) and expects array rows back.

  • We prepare a StatementSync, apply toggles like setReadBigInts, execute, and return arrays. On Node 24+, if arrays are already enabled (returnArrays/setReturnArrays), we skip the object→array conversion. (orm.drizzle.team, nodejs.org)


Minimal API surface

type Config<TSchema = unknown> = {
  client?: string | DatabaseSync
  databaseOptions?: ConstructorParameters<typeof DatabaseSync>[1]
  schema?: TSchema
  logger?: boolean
  readBigInts?: boolean
  allowBareNamedParameters?: boolean
  allowUnknownNamedParameters?: boolean
}

About

A tiny adapter that lets Drizzle ORM talk to Node’s built-in node:sqlite

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published