Skip to main content

Documentation Index

Fetch the complete documentation index at: https://effect-ts-effect-smol.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

Effect provides several SQLite packages optimized for different JavaScript runtimes. All packages share the same API but use runtime-specific SQLite implementations.

Available Packages

Node.js

Package: @effect/sql-sqlite-node Uses better-sqlite3 for synchronous, high-performance SQLite access.
npm install @effect/sql-sqlite-node better-sqlite3

Bun

Package: @effect/sql-sqlite-bun Uses Bun’s native, built-in SQLite support (no external dependencies).
bun add @effect/sql-sqlite-bun

WebAssembly

Package: @effect/sql-sqlite-wasm Uses wa-sqlite for browser and edge runtime support.
npm install @effect/sql-sqlite-wasm @effect/wa-sqlite

Quick Start

Node.js

import { SqliteClient } from "@effect/sql-sqlite-node"
import * as Effect from "effect/Effect"
import * as Sql from "effect/unstable/sql"

const SqliteLive = SqliteClient.layer({
  filename: "./mydb.db"
})

const program = Effect.gen(function* () {
  const sql = yield* Sql.SqlClient
  const users = yield* sql`SELECT * FROM users`
  return users
}).pipe(
  Effect.provide(SqliteLive)
)

Effect.runPromise(program).then(console.log)

Bun

import { SqliteClient } from "@effect/sql-sqlite-bun"
import * as Effect from "effect/Effect"
import * as Sql from "effect/unstable/sql"

const SqliteLive = SqliteClient.layer({
  filename: "./mydb.db"
})

// Same API as Node.js version
const program = Effect.gen(function* () {
  const sql = yield* Sql.SqlClient
  const users = yield* sql`SELECT * FROM users`
  return users
}).pipe(
  Effect.provide(SqliteLive)
)

WebAssembly (Browser)

import { SqliteClient } from "@effect/sql-sqlite-wasm"
import * as Effect from "effect/Effect"
import * as Sql from "effect/unstable/sql"

const SqliteLive = SqliteClient.layer({
  filename: "mydb.db" // Stored in browser memory or IndexedDB
})

const program = Effect.gen(function* () {
  const sql = yield* Sql.SqlClient
  const users = yield* sql`SELECT * FROM users`
  return users
}).pipe(
  Effect.provide(SqliteLive)
)

Effect.runPromise(program).then(console.log)

Configuration

Node.js Options

import { SqliteClient } from "@effect/sql-sqlite-node"

const SqliteLive = SqliteClient.layer({
  filename: "./mydb.db",
  
  // Use in-memory database
  // filename: ":memory:",
  
  // Read-only mode
  readonly: false,
  
  // Create if doesn't exist
  fileMustExist: false,
  
  // Performance options
  timeout: 5000,
  verbose: console.log, // Log all SQL
  
  // Name transformation
  transformResultNames: (str) => str,
  transformQueryNames: (str) => str
})

Bun Options

import { SqliteClient } from "@effect/sql-sqlite-bun"

const SqliteLive = SqliteClient.layer({
  filename: "./mydb.db",
  
  // Bun-specific options
  create: true,
  readwrite: true,
  
  // Name transformation
  transformResultNames: (str) => str,
  transformQueryNames: (str) => str
})

WASM Options

import { SqliteClient } from "@effect/sql-sqlite-wasm"

const SqliteLive = SqliteClient.layer({
  filename: "mydb.db",
  
  // WASM-specific options
  // Database persisted to IndexedDB by default
  
  transformResultNames: (str) => str,
  transformQueryNames: (str) => str
})

Basic Operations

Creating Tables

const createSchema = Effect.gen(function* () {
  const sql = yield* Sql.SqlClient
  
  yield* sql`
    CREATE TABLE IF NOT EXISTS users (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      name TEXT NOT NULL,
      email TEXT UNIQUE NOT NULL,
      created_at DATETIME DEFAULT CURRENT_TIMESTAMP
    )
  `
  
  yield* sql`
    CREATE INDEX IF NOT EXISTS idx_email ON users(email)
  `
})

Inserting Data

const insertUser = (name: string, email: string) =>
  Effect.gen(function* () {
    const sql = yield* Sql.SqlClient
    const result = yield* sql`
      INSERT INTO users (name, email)
      VALUES (${name}, ${email})
    `
    return result.lastInsertRowid
  })

const insertMany = (users: Array<{ name: string; email: string }>) =>
  Effect.gen(function* () {
    const sql = yield* Sql.SqlClient
    yield* sql`INSERT INTO users ${sql.insert(users)}`
  })

Querying Data

const getAllUsers = Effect.gen(function* () {
  const sql = yield* Sql.SqlClient
  return yield* sql`SELECT * FROM users`
})

const getUserById = (id: number) =>
  Effect.gen(function* () {
    const sql = yield* Sql.SqlClient
    const users = yield* sql`SELECT * FROM users WHERE id = ${id}`
    return users[0]
  })

const searchUsers = (query: string) =>
  Effect.gen(function* () {
    const sql = yield* Sql.SqlClient
    return yield* sql`
      SELECT * FROM users
      WHERE name LIKE ${'%' + query + '%'}
      ORDER BY name
    `
  })

Updating and Deleting

const updateUser = (id: number, name: string) =>
  Effect.gen(function* () {
    const sql = yield* Sql.SqlClient
    const result = yield* sql`
      UPDATE users 
      SET name = ${name}
      WHERE id = ${id}
    `
    return result.changes
  })

const deleteUser = (id: number) =>
  Effect.gen(function* () {
    const sql = yield* Sql.SqlClient
    const result = yield* sql`DELETE FROM users WHERE id = ${id}`
    return result.changes
  })

Transactions

const transferFunds = (from: number, to: number, amount: number) =>
  Effect.gen(function* () {
    const sql = yield* Sql.SqlClient
    
    yield* sql`
      UPDATE accounts 
      SET balance = balance - ${amount} 
      WHERE id = ${from}
    `
    
    yield* sql`
      UPDATE accounts 
      SET balance = balance + ${amount} 
      WHERE id = ${to}
    `
  }).pipe(
    sql.withTransaction
  )

SQLite-Specific Features

JSON Support (SQLite 3.38+)

const insertDocument = (data: unknown) =>
  Effect.gen(function* () {
    const sql = yield* Sql.SqlClient
    yield* sql`
      INSERT INTO documents (data)
      VALUES (${JSON.stringify(data)})
    `
  })

const queryJson = Effect.gen(function* () {
  const sql = yield* Sql.SqlClient
  return yield* sql`
    SELECT * FROM documents
    WHERE json_extract(data, '$.name') = 'John'
  `
})
// Create FTS5 table
const createFts = Effect.gen(function* () {
  const sql = yield* Sql.SqlClient
  yield* sql`
    CREATE VIRTUAL TABLE articles_fts 
    USING fts5(title, content)
  `
})

// Search
const searchArticles = (query: string) =>
  Effect.gen(function* () {
    const sql = yield* Sql.SqlClient
    return yield* sql`
      SELECT * FROM articles_fts
      WHERE articles_fts MATCH ${query}
      ORDER BY rank
    `
  })

UPSERT (SQLite 3.24+)

const upsertUser = (id: number, name: string, email: string) =>
  Effect.gen(function* () {
    const sql = yield* Sql.SqlClient
    yield* sql`
      INSERT INTO users (id, name, email)
      VALUES (${id}, ${name}, ${email})
      ON CONFLICT(id) DO UPDATE SET
        name = excluded.name,
        email = excluded.email
    `
  })

Window Functions

const getUserRankings = Effect.gen(function* () {
  const sql = yield* Sql.SqlClient
  return yield* sql`
    SELECT 
      name,
      score,
      RANK() OVER (ORDER BY score DESC) as rank
    FROM users
    ORDER BY score DESC
  `
})

Migrations

import { SqliteMigrator } from "@effect/sql-sqlite-node"
import * as Effect from "effect/Effect"
import * as Layer from "effect/Layer"

const MigratorLive = SqliteMigrator.layer({
  loader: SqliteMigrator.fromFileSystem("./migrations"),
  schemaDirectory: "sql/migrations"
})

const migrate = Effect.gen(function* () {
  const migrator = yield* SqliteMigrator
  yield* migrator.run()
}).pipe(
  Effect.provide(Layer.merge(SqliteLive, MigratorLive))
)

In-Memory Database

Useful for testing:
const TestDb = SqliteClient.layer({
  filename: ":memory:"
})

const testProgram = Effect.gen(function* () {
  // Create schema
  yield* createSchema
  
  // Run tests
  yield* insertUser("Alice", "alice@example.com")
  const users = yield* getAllUsers
  
  // Assert results...
}).pipe(
  Effect.provide(TestDb)
)

Performance Optimization

Pragma Settings

const optimizeDb = Effect.gen(function* () {
  const sql = yield* Sql.SqlClient
  
  // Enable WAL mode for better concurrency
  yield* sql`PRAGMA journal_mode = WAL`
  
  // Increase cache size (in pages)
  yield* sql`PRAGMA cache_size = -64000` // 64MB
  
  // Use memory for temp tables
  yield* sql`PRAGMA temp_store = MEMORY`
  
  // Synchronous mode for performance
  yield* sql`PRAGMA synchronous = NORMAL`
})

Batch Inserts

const batchInsert = (users: Array<{ name: string; email: string }>) =>
  Effect.gen(function* () {
    const sql = yield* Sql.SqlClient
    
    // Much faster than individual inserts
    yield* sql`INSERT INTO users ${sql.insert(users)}`
  }).pipe(
    sql.withTransaction // Use transaction for maximum speed
  )

Error Handling

import { SqlError } from "effect/unstable/sql/SqlError"
import * as Effect from "effect/Effect"

const safeInsert = (name: string, email: string) =>
  insertUser(name, email).pipe(
    Effect.catchTag("SqlError", (error) => {
      if (error.message.includes("UNIQUE constraint failed")) {
        return Effect.fail({ _tag: "UserExists" as const })
      }
      return Effect.fail({ _tag: "DatabaseError" as const, error })
    })
  )

Choosing a SQLite Package

PackageUse CasePerformanceDependencies
sql-sqlite-nodeNode.js appsFastestRequires native build
sql-sqlite-bunBun appsVery fastNone (built-in)
sql-sqlite-wasmBrowser/EdgeGoodPure JavaScript

Best Practices

  1. Use WAL mode: Better concurrency than default journal mode
  2. Batch operations: Use transactions for bulk inserts/updates
  3. Index strategically: Add indexes for frequently queried columns
  4. Analyze queries: Use EXPLAIN QUERY PLAN to optimize
  5. Vacuum regularly: Reclaim unused space with VACUUM
  6. Limit result sets: Use LIMIT for large queries

Requirements

  • Node.js: Node.js 18+, better-sqlite3 compatible
  • Bun: Bun 1.0+
  • WASM: Modern browser with WebAssembly support
  • Effect: 4.0.0 or higher

Next Steps