Local-first platform designed for privacy, ease of use, and no vendor lock-in
Get started · GitHub repository (opens in a new tab)
Features
- SQLite (opens in a new tab) in all browsers, Electron, and React Native
- CRDT (opens in a new tab) for merging changes without conflicts
- End-to-end encrypted sync and backup
- Free Evolu sync and backup server, or you can run your own
- Typed database schema (with branded types like
NonEmptyString1000
,PositiveInt
, etc.) - Typed SQL via Kysely (opens in a new tab)
- Reactive queries with full React Suspense support
- Real-time experience via revalidation on focus and network recovery
- No signup/login, only bitcoin-like mnemonic (12 words)
- Ad-hoc migration
- Sqlite JSON support with automatic stringifying and parsing
- Support for Kysely Relations (opens in a new tab) (loading nested objects and arrays in a single SQL query)
- Local-only tables (tables with _ prefix are not synced)
- Evolu Solid/Vue/Svelte soon
Overview
import * as S from "@effect/schema/Schema";
import * as Evolu from "@evolu/react";
// Create TodoId schema.
const TodoId = Evolu.id("Todo");
// It's branded string: string & Brand<"Id"> & Brand<"Todo">
// TodoId type ensures no other ID can be used where TodoId is expected.
type TodoId = S.Schema.To<typeof TodoId>;
// Create TodoTable schema.
const TodoTable = S.struct({
id: TodoId,
// Note we can enforce NonEmptyString1000.
title: Evolu.NonEmptyString1000,
// SQLite doesn't support the boolean type, so Evolu uses
// SqliteBoolean (a branded number) instead.
isCompleted: S.nullable(Evolu.SqliteBoolean),
});
type TodoTable = S.Schema.To<typeof TodoTable>;
// Create database schema.
const Database = S.struct({
todo: TodoTable,
});
// Create Evolu.
const evolu = Evolu.create(Database);
// Create a typed SQL query. Yes, with autocomplete and type-checking.
const allTodos = evolu.createQuery((db) =>
db
.selectFrom("todo")
.selectAll()
// SQLite doesn't support the boolean type, but we have `cast` helper.
.where("isDeleted", "is not", Evolu.cast(true))
.orderBy("createdAt"),
);
// Load the query. Batched and cached by default.
const allTodosPromise = evolu.loadQuery(allTodos);
// React Hooks.
const { useEvolu, useQuery } = evolu;
// Use the query in React reactively (it's updated on a mutation).
const { rows, row } = useQuery(allTodos);
// Create a todo.
const { create } = useEvolu();
create("todo", {
title: S.parseSync(Evolu.NonEmptyString1000)("Learn Effect"),
});
// Update a todo.
const { update } = useEvolu();
update("todo", { id, isCompleted: true });
// Delete all data on a device.
useEvolu().resetOwner();
// Restore all data on a different defice.
useEvolu().restoreOwner(mnemonic);
Community
The Evolu community is on GitHub Discussions (opens in a new tab).
To chat with other community members, you can join the Evolu Discord (opens in a new tab).