drizzle-orm-node-sqlite
v0.1.0
Published
Drizzle ORM adapter for Node's built-in node:sqlite
Maintainers
Readme
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:sqlitewas 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)
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)onStatementSyncand thereturnArrays/timeoutdatabase options were added in v24.x.DatabaseSyncAPIs 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 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.
Performance & concurrency tips
Synchronous API:
DatabaseSyncAPIs 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
timeoutinnew DatabaseSync(path, { timeout: 2000 })to reduceSQLITE_BUSYerrors 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 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)
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
}
