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)
-
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)
ornew DatabaseSync(path, { returnArrays: true })
. This adapter works either way; if arrays are already returned, it forwards them unchanged. (nodejs.org)
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
:
// 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)
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)
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)
-
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)
-
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)
onStatementSync
and thereturnArrays
/timeout
database options were added in v24.x.DatabaseSync
APIs execute synchronously. (nodejs.org)
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)
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)
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)
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 thedist/
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.
-
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
innew DatabaseSync(path, { timeout: 2000 })
to reduceSQLITE_BUSY
errors under contention. (nodejs.org)
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)
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)
-
Drizzle’s SQLite Proxy driver calls a function with
(sql, params, method)
and expects array rows back. -
We prepare a
StatementSync
, apply toggles likesetReadBigInts
, 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)
type Config<TSchema = unknown> = {
client?: string | DatabaseSync
databaseOptions?: ConstructorParameters<typeof DatabaseSync>[1]
schema?: TSchema
logger?: boolean
readBigInts?: boolean
allowBareNamedParameters?: boolean
allowUnknownNamedParameters?: boolean
}