Skip to content

Getting Started

web-sqlite-js is a friendly, out-of-the-box SQLite database for the web that makes persistent client-side storage simple for every developer.

Designed to be truly effortless, it allows you to get a high-performance relational database running in the browser in seconds. Just install, set your HTTP headers, and start querying—no complex infrastructure required.

Quick start

Before you begin, ensure your environment meets the Browser Compatibility requirements.

Option A: npm / bundler

bash
npm i web-sqlite-js
bash
pnpm add web-sqlite-js
bash
yarn add web-sqlite-js
bash
bun add web-sqlite-js
bash
deno add npm:web-sqlite-js
ts
import openDB from "web-sqlite-js";

// ...

Option B: CDN / script tag (no build step)

For quick demos or plain HTML pages you can load the prebuilt module directly:

html
<script type="module">
  import openDB from "https://cdn.jsdelivr.net/npm/web-sqlite-js@1.0.9/dist/index.js";
  // ...
</script>

See samples/cdn.html for a copy/paste page you can serve .

Heads up: SharedArrayBuffer requires COOP/COEP headers; see the section below.

Setup http headers

Pick your stack below to set the headers:

This library depends on SharedArrayBuffer for high performance, which requires your server to send the following HTTP headers:

http
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Vite

Update your vite.config.ts:

typescript
import { defineConfig } from "vite";

export default defineConfig({
  server: {
    headers: {
      "Cross-Origin-Opener-Policy": "same-origin",
      "Cross-Origin-Embedder-Policy": "require-corp",
    },
  },
  preview: {
    headers: {
      "Cross-Origin-Opener-Policy": "same-origin",
      "Cross-Origin-Embedder-Policy": "require-corp",
    },
  },
});
Next.js

Update your next.config.js:

javascript
/** @type {import('next').NextConfig} */
const nextConfig = {
  async headers() {
    return [
      {
        source: "/(.*)",
        headers: [
          {
            key: "Cross-Origin-Opener-Policy",
            value: "same-origin",
          },
          {
            key: "Cross-Origin-Embedder-Policy",
            value: "require-corp",
          },
        ],
      },
    ];
  },
};

module.exports = nextConfig;
Webpack (Dev Server)

Update your webpack.config.js:

javascript
module.exports = {
  // ...
  devServer: {
    headers: {
      "Cross-Origin-Opener-Policy": "same-origin",
      "Cross-Origin-Embedder-Policy": "require-corp",
    },
  },
};
Nginx

Add the headers to your server block:

nginx
server {
    # ...
    add_header Cross-Origin-Opener-Policy "same-origin";
    add_header Cross-Origin-Embedder-Policy "require-corp";
    # ...
}
Express.js

Use a middleware:

javascript
const express = require("express");
const app = express();

app.use((req, res, next) => {
  res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
  res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
  next();
});

// ...
React / Vue (Create React App / Vue CLI)

Most modern React/Vue setups use Vite. Please refer to the Vite section above.

If you are using an older webpack-based setup (like CRA react-scripts), you technically need to configure the underlying webpack-dev-server, but CRA doesn't expose this easily without ejecting or using tools like craco or react-app-rewired to modify the dev server configuration as shown in the Webpack section.

Usage

Basic Usage

typescript
// 1. Open the database (creates 'my-database.sqlite3' in OPFS)
const db = await openDB("local.sqlite3");

// 2. Initialize schema
await db.exec(`
  CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT,
    email TEXT
  );
`);

// 3. Insert data (Parameterized)
await db.exec("INSERT INTO users (name, email) VALUES (?, ?)", [
  "Alice",
  "alice@example.com",
]);
await db.exec("INSERT INTO users (name, email) VALUES ($name, $email)", {
  $name: "Bob",
  $email: "bob@example.com",
});

// 4. Query data

const users = await db.query("SELECT * FROM users");
console.log(users);
// Output: [{ id: 1, name: 'Alice', ... }, { id: 2, name: 'Bob', ... }]

// 5. Close when done
await db.close();

Debug mode

Enable { debug: true } when opening the database to stream worker-side SQL logs (including bind values and timings) to the browser console. It is ideal for profiling and verifying queries during development.

ts
const db = await openDB("local.sqlite3", { debug: true });

await db.exec("CREATE TABLE IF NOT EXISTS notes (body TEXT)");
await db.query("SELECT * FROM notes WHERE id = ?", [1]);

The console output highlights SQL keywords and shows the duration of each statement (click to preview):

Debug console output

Transactions

Transactions are atomic. If any command inside the callback fails, the entire transaction is rolled back.

typescript
await db.transaction(async (tx) => {
  await tx.exec("INSERT INTO users (name) VALUES (?)", ["Charlie"]);

  // You can perform multiple operations safely
  await tx.exec("INSERT INTO logs (action) VALUES (?)", ["User Created"]);

  // If you throw an error here, both INSERTs will be rolled back!
  // throw new Error('Something went wrong');
});

Logging

For more advanced logging and monitoring, you can subscribe to structured log events from database operations.

Subscribe to Log Events

typescript
const unsubscribe = db.onLog((log) => {
  console.log(`[${log.level}]`, log.data);
});

// Logs include:
// - SQL execution with timing: {sql: "SELECT * FROM users", duration: 0.28, bind: []}
// - Transaction events: {action: "commit", sql: "COMMIT"}
// - Errors: {error: "SQLITE_CONSTRAINT: UNIQUE constraint failed", sql: "..."}
// - Application events: {action: "open", dbName: "myapp.sqlite3"}

// Later: unsubscribe();

Filter by Log Level

typescript
db.onLog((log) => {
  if (log.level === "error") {
    // Send to error tracking service
    errorTracker.capture(log.data);
  } else if (log.level === "debug") {
    // Log SQL execution details
    console.log("SQL:", log.data.sql, "Duration:", log.data.duration);
  }
});

Released under the MIT License.