Grafeo graph database in the browser.
Zero backend. Data stays on the client.
- Zero backend: Grafeo runs entirely in the browser via WebAssembly
- Persistent storage: IndexedDB keeps data across sessions
- Non-blocking: Web Worker execution keeps the UI responsive
- Multi-language queries: GQL, Cypher, SPARQL, SQL, Gremlin, GraphQL
- Parameterized queries: bind
$name-style parameters safely - Vector search: HNSW indexes with k-NN and MMR search
- Full-text search: BM25 text indexes with hybrid (text + vector) search
- Bulk import: LPG nodes/edges, RDF triples, or tabular rows
- Framework integrations: React, Vue, Svelte
- TypeScript-first: Complete type definitions
npm install @grafeo-db/webimport { GrafeoDB } from '@grafeo-db/web';
// In-memory database
const db = await GrafeoDB.create();
// Or persist to IndexedDB
const db = await GrafeoDB.create({ persist: 'my-database' });
// Create data
await db.execute(`INSERT (:Person {name: 'Alice', age: 30})`);
await db.execute(`INSERT (:Person {name: 'Bob', age: 25})`);
await db.execute(`
MATCH (a:Person {name: 'Alice'}), (b:Person {name: 'Bob'})
INSERT (a)-[:KNOWS {since: 2020}]->(b)
`);
// Query
const result = await db.execute(`
MATCH (p:Person)-[:KNOWS]->(friend)
RETURN p.name, friend.name
`);
for (const row of result) {
console.log(`${row['p.name']} knows ${row['friend.name']}`);
}
// Check version
console.log(GrafeoDB.version()); // e.g. "0.5.27"
// Cleanup
await db.close();// GQL (default)
await db.execute(`MATCH (p:Person) RETURN p.name`);
// Cypher
await db.execute(`MATCH (p:Person) RETURN p.name`, { language: 'cypher' });
// SPARQL
await db.execute(`SELECT ?name WHERE { ?p a :Person ; :name ?name }`, { language: 'sparql' });
// SQL
await db.execute(`SELECT name FROM Person`, { language: 'sql' });Supported: gql, cypher, sparql, sql, gremlin, graphql.
const result = await db.execute(
`MATCH (p:Person {name: $name}) RETURN p.age`,
{ params: { name: 'Alice' } },
);Parameters work with all query languages and in the lite build.
| Method | Description |
|---|---|
GrafeoDB.create(options?) |
Create a database instance |
GrafeoDB.version() |
Get the WASM engine version |
db.execute(query, options?) |
Execute a query, returns Record<string, unknown>[] |
db.executeRaw(query, options?) |
Execute a query, returns columns + rows + timing |
db.nodeCount() |
Number of nodes |
db.edgeCount() |
Number of edges |
db.schema() |
Schema info: labels, edge types, property keys |
db.memoryUsage() |
Hierarchical WASM heap usage breakdown |
db.setSchema(name) |
Set current schema context |
db.resetSchema() |
Clear current schema context |
db.currentSchema() |
Get current schema name (or undefined) |
db.clearPlanCache() |
Clear cached query plans |
db.isOpen |
Whether the database is still open |
db.close() |
Release WASM memory and cleanup |
| Method | Description |
|---|---|
db.export() |
Export full database as a snapshot |
db.import(snapshot) |
Restore from a snapshot |
db.clear() |
Delete all data |
db.storageStats() |
IndexedDB usage and quota |
| Method | Description |
|---|---|
db.importRows(rows, options) |
Import an array of objects as nodes or edges |
db.importLpg(data) |
Import LPG nodes and edges in one call |
db.importRdf(data) |
Import RDF triples (requires rdf WASM feature) |
| Method | Description |
|---|---|
db.createVectorIndex(label, property, options?) |
Create an HNSW vector index |
db.dropVectorIndex(label, property) |
Drop a vector index |
db.rebuildVectorIndex(label, property) |
Drop and recreate, preserving config |
db.vectorSearch(label, property, query, k, options?) |
k-NN search returning [{id, distance}] |
db.mmrSearch(label, property, query, k, options?) |
MMR search for diverse results |
| Method | Description |
|---|---|
db.createTextIndex(label, property) |
Create a BM25 text index |
db.dropTextIndex(label, property) |
Drop a text index |
db.rebuildTextIndex(label, property) |
Rebuild a text index |
db.textSearch(label, property, query, k) |
Full-text search returning [{id, score}] |
db.hybridSearch(label, textProp, vectorProp, queryText, k) |
Combined BM25 + vector search |
{
persist?: string; // IndexedDB key for persistence
worker?: boolean; // Run WASM in a Web Worker
persistInterval?: number; // Debounce interval in ms (default: 1000)
}{
language?: 'gql' | 'cypher' | 'sparql' | 'sql' | 'gremlin' | 'graphql';
params?: Record<string, unknown>; // $name-style parameters
}Data persists to IndexedDB automatically:
// First visit - creates database
const db = await GrafeoDB.create({ persist: 'my-app' });
await db.execute(`INSERT (:User {name: 'Alice'})`);
// Later visit - data is still there
const db = await GrafeoDB.create({ persist: 'my-app' });
const result = await db.execute(`MATCH (u:User) RETURN u.name`);
// -> [{ 'u.name': 'Alice' }]Persistence only triggers on mutating queries (INSERT, CREATE, DELETE, etc.), not on reads.
// Check storage usage
const stats = await db.storageStats();
console.log(`Using ${stats.bytesUsed} of ${stats.quota} bytes`);
// Export database
const snapshot = await db.export();
// Import into another database
const db2 = await GrafeoDB.create();
await db2.import(snapshot);
// Clear all data
await db.clear();For large databases or complex queries, run in a Web Worker:
const db = await GrafeoDB.create({
worker: true,
persist: 'large-database',
});
// Queries run in background thread - UI stays responsive
const result = await db.execute(`MATCH (a)-[*1..5]->(b) RETURN count(*)`);await db.importLpg({
nodes: [
{ labels: ['Person'], properties: { name: 'Alice' } },
{ labels: ['Person'], properties: { name: 'Bob' } },
],
edges: [
{ source: 0, target: 1, type: 'KNOWS', properties: { since: 2020 } },
],
});Edge source/target are zero-based indexes into the nodes array from the same batch.
// Import as nodes
await db.importRows(
[{ name: 'Alice', age: 30 }, { name: 'Bob', age: 25 }],
{ mode: 'nodes', label: 'Person' },
);
// Import as edges
await db.importRows(
[{ source: 1, target: 2, weight: 0.5 }],
{ mode: 'edges', edgeType: 'CONNECTS' },
);await db.importRdf({
triples: [
{ subject: 'http://ex.org/alice', predicate: 'http://ex.org/name', object: { value: 'Alice' } },
{ subject: 'http://ex.org/alice', predicate: 'http://ex.org/knows', object: 'http://ex.org/bob' },
],
});import { useGrafeo, useQuery } from '@grafeo-db/web/react';
function App() {
const { db, loading, error } = useGrafeo({ persist: 'my-app' });
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <PersonList db={db} />;
}
function PersonList({ db }) {
const { data, loading, refetch } = useQuery(
db,
`MATCH (p:Person) RETURN p.name`,
);
if (loading) return <div>Loading...</div>;
return (
<ul>
{data.map((row, i) => (
<li key={i}>{row['p.name']}</li>
))}
</ul>
);
}<script setup>
import { useGrafeo, useQuery } from '@grafeo-db/web/vue';
const { db, loading, error } = useGrafeo({ persist: 'my-app' });
const { data } = useQuery(db, `MATCH (p:Person) RETURN p.name`);
</script><script>
import { createGrafeo } from '@grafeo-db/web/svelte';
const { db, loading, error } = createGrafeo({ persist: 'my-app' });
</script>
{#if $loading}Loading...{/if}
{#if $error}Error: {$error.message}{/if}A smaller build with GQL support only (no multi-language parsers or AI search features):
import { GrafeoDB } from '@grafeo-db/web/lite';
const db = await GrafeoDB.create();
await db.execute(`MATCH (n) RETURN n`);
// Parameterized queries work in lite too
await db.execute(`MATCH (p {name: $name}) RETURN p`, { params: { name: 'Alice' } });| Browser | Version |
|---|---|
| Chrome | 89+ |
| Firefox | 89+ |
| Safari | 15+ |
| Edge | 89+ |
Requires WebAssembly, IndexedDB and Web Workers.
| Constraint | Limit |
|---|---|
| Database size | ~500 MB (IndexedDB quota) |
| Memory | ~256 MB (WASM heap) |
| Concurrency | Single writer, multiple readers |
changesSince() |
Returns [] (pending WASM change tracking) |
For larger datasets, use Grafeo server-side.
npm run build # Build all entries via tsup
npm test # Run tests (vitest, 145 tests)
npm run typecheck # Type check (tsc --noEmit)| Package | Use Case |
|---|---|
grafeo |
Rust crate |
@grafeo-db/wasm |
Raw WASM binary |
Apache-2.0