# Running MariaDB in the Browser with WebAssembly

> Source: <https://lite4mariadb.shyim.de/>
> Published: 2026-09-02 16:38:59+00:00

# MariaDB, compiled to WebAssembly.Real InnoDB. No server.

A full MariaDB embedded server that runs in the browser and in Node.js, with a PGlite-style JavaScript API. Transactions, foreign keys, window functions, CTEs, JSON and vector search — in one ~17 MB module.

**InnoDB**

Transactions, foreign keys, crash recovery. The real storage engine, not a shim.

**memory · file · idb**

Ephemeral by default, a directory in Node, IndexedDB in the browser. Snapshots as gzipped tar.

**MariaDB 13.1**

Window functions, CTEs, JSON functions and native vector search, unchanged.

**Browser + Node ≥ 18**

Main thread or a worker with the same API. One .wasm module plus a thin wrapper.

``` js
import { Lite4MariaDB } from 'lite4mariadb';

const db = await Lite4MariaDB.create({ dataDir: './my-data' });

db.exec('CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(64)) ENGINE=InnoDB');
db.exec('INSERT INTO users VALUES (?, ?)', [1, "O'Brien"]);

const rows = db.query('SELECT * FROM users WHERE id = ?', [1]);
// => [ { id: 1, name: "O'Brien" } ]

await db.close();
```

A datadir that already holds a database is resumed on open — InnoDB recovery runs, and your data is back.

``` js
import { Lite4MariaDB } from 'lite4mariadb';

// one IndexedDB database per name
const db = await Lite4MariaDB.create({ dataDir: 'idb://my-app' });

db.exec('CREATE TABLE notes (id INT AUTO_INCREMENT PRIMARY KEY, body TEXT)');
db.exec('INSERT INTO notes (body) VALUES (?)', ['hello from the tab']);

await db.persist();   // hard flush; writes are otherwise debounced
await db.close();
```

The build uses pthreads, so serve with Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp.

``` js
import { Lite4MariaDBWorker } from 'lite4mariadb/worker';

// same API, every method async, main thread stays free
const db = await Lite4MariaDBWorker.create({ dataDir: 'idb://my-app' });

const rows = await db.query('SELECT * FROM users');
await db.close();
```

Works in Node via worker_threads too. Pass your own Worker running dist/worker-entry.mjs as the second argument to customize loading.

| create(opts?) | memory:// (default), file:// in Node, idb:// in the browser |
| query(sql, params?) | Rows as objects, coerced JS types |
| exec(sql, params?) | { ok, affected, rows, fields } |
| execMulti(sql) | A whole script, one result per statement |
| transaction(cb) | BEGIN / COMMIT, ROLLBACK on throw |
| dumpDataDir() | Gzipped tar snapshot, restorable via loadDataDir |
| close() | Flushes idb:// first |
