Quickstart: in the browser
A database in the page. @fenecdb/web is the engine as
WebAssembly and a dependency-free client, so a script opens a database, writes
rows and queries them with no server. A few lines and a Vite project, and about
five minutes.
You need Node and npm. Nothing here leaves your browser, and the Playground is the same engine in this tab if you would rather type than install.
-
Make a project and install the package.
npm create vite@latest my-app -- --template vanilla --no-immediate cd my-app npm install @fenecdb/webWith
--templateand--no-immediateVite asks nothing and starts nothing: it makes the folder and stops, and the install brings Vite along with the package. -
Replace
src/main.jswith this. It opens a database, makes a collection, writes two rows and shows what two queries return: the rows from 2020 on, newest first, and a search for a word spelled wrong.src/main.jsimport { Fenec } from '@fenecdb/web'; import wasm from '@fenecdb/web/fenec.wasm?url'; const db = await Fenec.open(wasm); db.run('create collection notes (title text @text, year int @sorted)'); db.run('put notes {title: $1, year: $2}', ['Dunes', 2021]); db.run('put notes {title: $1, year: $2}', ['Night at the oasis', 2024]); const { rows } = db.run('get notes where year >= $1 order year desc', [2020]); const hits = db.rows('get notes select title match title $1 fuzzy', ['oasys']); const out = document.createElement('pre'); out.textContent = JSON.stringify(rows) + '\n' + JSON.stringify(hits); document.body.append(out); -
Start the dev server.
npm run devprints> my-app@0.0.0 dev > vite VITE v8.3.5 ready in 1161 ms ➜ Local: http://localhost:5173/ ➜ Network: use --host to expose -
Open
http://localhost:5173. The page shows both answers, as JSON.the page[{"id":2,"title":"Night at the oasis","year":2024},{"id":1,"title":"Dunes","year":2021}] [{"title":"Night at the oasis","_score":0.62239116}]
- The module is a URL.
Fenec.opentakes the address offenec.wasm, which Vite gives for a file imported with?url;npm run buildcopies it intodist/assetsand the page asks for it there. No Vite configuration is needed. db.runis synchronous: it returns the rows and not a promise, since a statement is one call into the module.db.rowsisrunwith the rows alone.- The query builder writes the same statements, each value a
parameter:
await db.from('notes').where('year', '>=', 2020).order('year', 'desc').rows()returns the first answer's rows (The query builder).
Keep it between visits
The database lives in the tab's memory: a reload starts it empty, and the
script above makes the collection again. persist stores it in
IndexedDB, the image once and then only the writes since, and
restore reads it back. This version writes the rows on the first
visit alone, and says which visit it is.
import { Fenec, persist, restore } from '@fenecdb/web';
import wasm from '@fenecdb/web/fenec.wasm?url';
const db = await Fenec.open(wasm);
const kept = await restore(db); // what an earlier visit stored, if one did
if (!kept) {
db.run('create collection notes (title text @text, year int @sorted)');
db.run('put notes {title: $1, year: $2}', ['Dunes', 2021]);
db.run('put notes {title: $1, year: $2}', ['Night at the oasis', 2024]);
await persist(db); // into IndexedDB
}
const { rows } = db.run('get notes where year >= $1 order year desc', [2020]);
const hits = db.rows('get notes select title match title $1 fuzzy', ['oasys']);
const out = document.createElement('pre');
out.textContent = (kept ? 'restored from IndexedDB' : 'first visit') + '\n'
+ JSON.stringify(rows) + '\n' + JSON.stringify(hits);
document.body.append(out);Save it and the page reloads on its own and says first visit.
Reload it once more, and the rows come from IndexedDB.
first visit
[{"id":2,"title":"Night at the oasis","year":2024},{"id":1,"title":"Dunes","year":2021}]
[{"title":"Night at the oasis","_score":0.62239116}]restored from IndexedDB
[{"id":2,"title":"Night at the oasis","year":2024},{"id":1,"title":"Dunes","year":2021}]
[{"title":"Night at the oasis","_score":0.62239116}]A page that writes later calls persist(db) after the write. To
start again, clear the site's data in your browser's developer tools.
No bundler, no npm, and Node
Another bundler does what Vite does: import @fenecdb/web and
hand Fenec.open the module's URL, however that bundler spells a
file's address. A page whose queries run on a server imports
@fenecdb/web/client and loads no module at all (Which package).
With no bundler, any static file server shows the same page: serve
node_modules as it is and hand Fenec.open the module's
path. Save this as index.html beside node_modules, run
python3 -m http.server 8000 (or npx serve) and open
http://localhost:8000; it shows the first visit's two lines.
<!doctype html>
<meta charset="utf-8">
<pre id="out">loading…</pre>
<script type="module">
import { Fenec } from './node_modules/@fenecdb/web/fenec.js';
const db = await Fenec.open('./node_modules/@fenecdb/web/fenec.wasm');
db.run('create collection notes (title text @text, year int @sorted)');
db.run('put notes {title: $1, year: $2}', ['Dunes', 2021]);
db.run('put notes {title: $1, year: $2}', ['Night at the oasis', 2024]);
const { rows } = db.run('get notes where year >= $1 order year desc', [2020]);
const hits = db.rows('get notes select title match title $1 fuzzy', ['oasys']);
document.querySelector('#out').textContent =
JSON.stringify(rows) + '\n' + JSON.stringify(hits);
</script>It does not open over file://. A page opened from disk
stays on loading…: the browser refuses a module
import from a page with no HTTP origin. Any static server
works: python3 -m http.server, npx serve,
nginx.
No npm: the same files, the module and the collate/ data beside
it, are fenec-web-v0.1.12.tar.gz on the release.
In Node, where fetch cannot resolve a relative path, hand
Fenec.open the bytes:
import { readFile } from 'node:fs/promises';
import { Fenec } from '@fenecdb/web';
const wasm = await readFile(new URL(import.meta.resolve('@fenecdb/web/fenec.wasm')));
const db = await Fenec.open(wasm);
db.run('create collection notes (title text @text, year int @sorted)');
db.run('put notes {title: $1, year: $2}', ['Dunes', 2021]);
db.run('put notes {title: $1, year: $2}', ['Night at the oasis', 2024]);
console.log(db.run('get notes where year >= $1 order year desc', [2020]).rows);[
{ id: 2, title: 'Night at the oasis', year: 2024 },
{ id: 1, title: 'Dunes', year: 2021 }
]Next
- JavaScript and TypeScript
The query builder, live queries as an app's state, a database kept in a file of the page, a schema declared in code, and which package a page needs.
- Sync
A replica in the page that reads without the network and writes on its own, with a server keeping every page's writes.
- Quickstart: the server
The same collection on
fenec-server, reached over HTTP from any language, with a page that runs no module at all. - FenecQL
The language: a tour from the first write to a lookup and a total.