Evolu - React Hooks for Local-First Apps

React Hooks library for local-first apps (opens in a new tab) with end-to-end encrypted backup and sync using SQLite (opens in a new tab) and CRDT (opens in a new tab).

Local-first apps allow users to own their data. Evolu stores data in the user's device(s), so Evolu apps can work offline.

Evolu merges data changes made on different devices without conflicts using Conflict-free Replicated Data Type (CRDT). How Evolu Works

ℹ️

Evolu is designed for privacy, ease of use, and no vendor lock-in.

Getting Started · GitHub Repository (opens in a new tab) · Twitter (opens in a new tab)

Overview

import * as Schema from "@effect/schema/Schema";
import * as Evolu from "evolu";
 
const TodoId = Evolu.id("Todo");
type TodoId = Schema.To<typeof TodoId>;
 
const NonEmptyString50 = Schema.string.pipe(
  Schema.minLength(1),
  Schema.maxLength(50),
  Schema.brand("NonEmptyString50"),
);
type NonEmptyString50 = Schema.To<typeof NonEmptyString50>;
 
const TodoTable = Schema.struct({
  id: TodoId,
  title: NonEmptyString50,
  description: Schema.nullable(Evolu.NonEmptyString1000),
  isCompleted: Evolu.SqliteBoolean,
});
type TodoTable = Schema.To<typeof TodoTable>;
 
const Database = Schema.struct({
  todo: TodoTable,
});
 
const { useQuery, useMutation } = Evolu.create(Database);
 
const { rows } = useQuery(
  (db) =>
    // A type-safe SQL query builder.
    db.selectFrom("todo").select(["id", "title"]).orderBy("createdAt"),
  // The filterMap helper for ad-hoc schema migrations.
  (row) => row
);
 
const { create, update } = useMutation();
 
if (!Schema.is(NonEmptyString50)(title)) return;
const { id } = create("todo", { title, isCompleted: false });
 
update("todo", { id, isCompleted: true }, onComplete);

Features

  • The official SQLite Wasm in all browsers and Electron (React Native soon)
  • E2E encrypted sync and backup with CRDT (merging changes without conflicts)
  • Free Evolu server for testing (paid production-ready soon, or you can run your own)
  • Typed database schema with branded types (NonEmptyString1000, PositiveInt)
  • Reactive queries with React Suspense support
  • Real-time experience via revalidation on focus and network recovery
  • Schema evolving via filterMap ad-hoc migration
  • No signup/login, no email collection, only Bitcoin-like mnemonic (12 words)

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).