---
title: "SQLite"
url: "https://effect-auth.itsbroly.com/storage/sqlite/"
description: "Choose and operate an Effect Auth SQLite adapter across Node, Bun, Durable Objects, WASM, or an application-owned executor."
---



Start with the database that will hold authentication records. Effect Auth owns the SQLite schema and queries; your application owns the database file or platform database, migration history, backups, and runtime lifecycle. Then choose the thinnest adapter that matches how that database is already accessed.

<CalloutContainer type="info">
  <CalloutTitle>
    Database first
  </CalloutTitle>

  <CalloutDescription>
    Already have an `effect-qb/sqlite` executor? Use it directly. Already have an Effect Drizzle SQLite database, or need a packaged runtime driver? Use the Drizzle bridge. Neither choice changes the auth schema or storage behavior.
  </CalloutDescription>
</CalloutContainer>

## Runtime matrix [#runtime-matrix]

| Database/runtime                             | Adapter path                                          | Exact public constructor                                                            | Ownership                                                                   |
| -------------------------------------------- | ----------------------------------------------------- | ----------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| Application-provided generic SQLite executor | `@effect-auth/core/EffectQbSqliteStorage`             | `EffectQbSqliteAuthStorageLive(executor, options?)`                                 | Application opens, configures, and closes the `effect-qb/sqlite` executor   |
| Node SQLite                                  | `@effect-auth/core/DrizzleNodeSqliteStorage`          | `DrizzleNodeSqliteAuthStorageLive(sqliteConfig, options?, drizzleConfig?)`          | Constructor creates a scoped `@effect/sql-sqlite-node` client               |
| Bun SQLite                                   | `@effect-auth/core/DrizzleBunSqliteStorage`           | `DrizzleBunSqliteAuthStorageLive(sqliteConfig, options?, drizzleConfig?)`           | Constructor creates a scoped `@effect/sql-sqlite-bun` client                |
| Durable Object SQLite                        | `@effect-auth/core/DrizzleDurableObjectSqliteStorage` | `DrizzleDurableObjectSqliteAuthStorageLive(sqliteConfig, options?, drizzleConfig?)` | Pass `{ storage: ctx.storage.sql }`; one database belongs to each object    |
| Persistent WASM SQLite                       | `@effect-auth/core/DrizzleWasmSqliteStorage`          | `DrizzleWasmSqliteAuthStorageLive(sqliteConfig, options?, drizzleConfig?)`          | Scoped client; persistence belongs to the configured worker/storage backend |
| In-memory WASM SQLite                        | `@effect-auth/core/DrizzleWasmSqliteStorage`          | `DrizzleWasmMemorySqliteAuthStorageLive(sqliteConfig?, options?, drizzleConfig?)`   | Scoped and ephemeral; tests or disposable sessions only                     |

If the application already owns a compatible Effect Drizzle database, use `DrizzleEffectSqliteAuthStorageLive(database, options?)` from `@effect-auth/core/DrizzleEffectSqliteStorage`; the adapter neither acquires nor closes it.

Cloudflare D1 is SQLite-compatible but operationally different from local SQLite. Use the dedicated [Cloudflare D1](/storage/cloudflare-d1/) guide for bindings, deployment, and limits rather than applying this page's local lifecycle assumptions.

## Query path [#query-path]

```text
Native executor                      Drizzle bridge

Auth stores                          Auth stores
    |                                    |
Effect-QB SQLite queries             Effect-QB SQLite queries
    |                                    |
effect-qb/sqlite Executor            makeDrizzleEffectSqliteExecutor
    |                                    |
application SQLite driver            Effect Drizzle database -> SQLite
```

The native path is the smallest surface. The Drizzle path translates placeholders into Drizzle SQL and calls the database's Effect-style `all(...)`; it then delegates to the same Effect-QB store implementation. Use Drizzle for an existing Drizzle stack or its maintained runtime clients, not for a different schema or dialect.

## Minimal Node setup [#minimal-node-setup]

Apply migrations before startup, then keep the layer inside one long-lived Effect scope:

```ts
import { DrizzleNodeSqliteAuthStorageLive } from "@effect-auth/core/DrizzleNodeSqliteStorage";
import { UserId } from "@effect-auth/core/Identifiers";
import { UserStore } from "@effect-auth/core/Storage";
import { Effect } from "effect";

const AuthStorageLive = DrizzleNodeSqliteAuthStorageLive({
  filename: "./data/auth.sqlite",
});

const program = Effect.gen(function* () {
  const users = yield* UserStore;
  return yield* users.findById(UserId("01J00000000000000000000000"));
});

await Effect.runPromise(
  Effect.scoped(program.pipe(Effect.provide(AuthStorageLive))),
);
```

For Bun, switch to `DrizzleBunSqliteAuthStorageLive` from `@effect-auth/core/DrizzleBunSqliteStorage`; its configuration has the same role. Install only the matching optional Effect SQL driver and Drizzle peers.

## Migrations and lifecycle [#migrations-and-lifecycle]

`authStorageMigrations` from `@effect-auth/core/StorageMigrations` is the authoritative ordered schema. Your deployment or application migration system must apply every entry exactly once, record its `id`, and finish before auth traffic starts. Adapters never migrate automatically. Drizzle Kit schemas and `drizzle-kit push` are not substitutes for these migrations.

Create one scoped Node, Bun, or WASM layer per application runtime, not per query. Scope finalization closes its client. Application-provided Effect-QB and Drizzle databases remain application-owned. In a Durable Object, construct and consume the layer within that object's lifecycle and choose object IDs so records that must be queried together share one database.

## Concurrency essentials [#concurrency-essentials]

* SQLite allows concurrent reads but serializes writes. Keep transactions and indexed auth writes short; handle busy/lock failures according to your retry policy.
* WAL can improve local reader/writer overlap, but is not safe to assume on network or serverless filesystems.
* A Durable Object serializes work per object, not across object IDs. WASM permits competing writers only when its persistence backend explicitly supports them.
* The adapter does not add a global mutex or make several store calls atomic. Put cross-call transactions at a database boundary supported by your driver.

For a non-SQLite database or application-specific persistence model, implement the service contracts in [Custom Database](/storage/custom-database/).

