drizzle-orm-node-sqlite
Version:
Drizzle ORM adapter for Node's built-in node:sqlite
290 lines (182 loc) • 10.4 kB
Markdown
# 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](https://orm.drizzle.team/docs/connect-drizzle-proxy "Drizzle ORM - Drizzle Proxy"))
----------
## Requirements
- **Node.js ≥ 22.5.0** (when `node:sqlite` was introduced; stability “Active development”). ([nodejs.org](https://nodejs.org/api/sqlite.html "SQLite | Node.js v24.6.0 Documentation"))
- **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](https://nodejs.org/api/sqlite.html "SQLite | Node.js v24.6.0 Documentation"))
>
----------
## Install
```bash
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`:
```jsonc
// tsconfig.json
{
"compilerOptions": {
"moduleResolution": "NodeNext",
"types": ["node"]
}
}
```
----------
## Quick start
```ts
// 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](https://orm.drizzle.team/docs/rqb?utm_source=chatgpt.com "Drizzle ORM - Query"))
----------
## Example
```ts
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:
```ts
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](https://nodejs.org/api/sqlite.html "SQLite | Node.js v24.6.0 Documentation"))
----------
## 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](https://orm.drizzle.team/docs/connect-drizzle-proxy "Drizzle ORM - Drizzle Proxy"))
### Node 24 (v24.x)
- You **can** opt into array rows and skip conversion:
```ts
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](https://nodejs.org/api/sqlite.html "SQLite | Node.js v24.6.0 Documentation"))
----------
## 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):**
```ts
import { initTRPC } from '@trpc/server'
import superjson from 'superjson'
export const t = initTRPC.create({ transformer: superjson })
```
**tRPC (client):**
```ts
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](https://trpc.io/docs/server/data-transformers?utm_source=chatgpt.com "Data Transformers - tRPC"))
If you don’t need exact precision, set `readBigInts: false` and you’ll get JS numbers instead. ([nodejs.org](https://nodejs.org/api/sqlite.html "SQLite | Node.js v24.6.0 Documentation"))
----------
## Migrations (Drizzle Kit)
Keep using the SQLite dialect and point Drizzle Kit at the same file path:
```ts
// 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](https://orm.drizzle.team/docs/drizzle-kit-migrate?utm_source=chatgpt.com "Drizzle ORM - `migrate`"))
----------
## 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](https://orm.drizzle.team/docs/connect-drizzle-proxy "Drizzle ORM - Drizzle Proxy"))
----------
## Development
To contribute, first set up the development environment:
```bash
# 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](https://nodejs.org/api/sqlite.html "SQLite | Node.js v24.6.0 Documentation"))
- **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:
```ts
sqlite.exec(`PRAGMA journal_mode = WAL;`)
```
(See SQLite’s WAL docs for trade-offs.) ([www3.sqlite.org](https://www3.sqlite.org/wal.html?utm_source=chatgpt.com "Write-Ahead Logging - SQLite"))
- **Busy timeout (Node 24+):** You can set `timeout` in `new DatabaseSync(path, { timeout: 2000 })` to reduce `SQLITE_BUSY` errors under contention. ([nodejs.org](https://nodejs.org/api/sqlite.html "SQLite | Node.js v24.6.0 Documentation"))
----------
## 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](https://nodejs.org/api/sqlite.html "SQLite | Node.js v24.6.0 Documentation"))
----------
## 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](https://orm.drizzle.team/docs/connect-drizzle-proxy "Drizzle ORM - Drizzle Proxy"))
**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](https://orm.drizzle.team/docs/rqb?utm_source=chatgpt.com "Drizzle ORM - Query"))
**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](https://trpc.io/docs/server/data-transformers?utm_source=chatgpt.com "Data Transformers - tRPC"))
----------
## 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](https://orm.drizzle.team/docs/connect-drizzle-proxy "Drizzle ORM - Drizzle Proxy"), [nodejs.org](https://nodejs.org/api/sqlite.html "SQLite | Node.js v24.6.0 Documentation"))
----------
## Minimal API surface
```ts
type Config<TSchema = unknown> = {
client?: string | DatabaseSync
databaseOptions?: ConstructorParameters<typeof DatabaseSync>[1]
schema?: TSchema
logger?: boolean
readBigInts?: boolean
allowBareNamedParameters?: boolean
allowUnknownNamedParameters?: boolean
}
```