The new version of Evolu is now available on npm under the preview tag. 🎉
Quickstart
This guide will get you all set up and ready to use Evolu.
Requirements: TypeScript 5.7
or later with the strict
and
exactOptionalPropertyTypes
flags enabled in tsconfig.json
file.
Installation
Evolu offers SDKs for a variety of frameworks, including React, Svelte, React Native, Expo, and others. Below, you can see how to install the SDKs for each framework.
npm install @evolu/common@preview @evolu/react@preview @evolu/react-web@preview
Define your Schema
First, define your database schema—tables, columns, and types.
Evolu uses Type for data modeling. Instead of plain JS types like string or number, we recommend using branded types to enforce domain rules.
schema.ts
import {
FiniteNumber,
id,
json,
maxLength,
NonEmptyString,
NonEmptyString1000,
nullOr,
object,
SqliteBoolean,
} from "@evolu/common";
const TodoId = id("Todo");
type TodoId = typeof TodoId.Type;
const TodoCategoryId = id("TodoCategory");
type TodoCategoryId = typeof TodoCategoryId.Type;
// A custom branded Type.
const NonEmptyString50 = maxLength(50)(NonEmptyString);
// string & Brand<"MinLength1"> & Brand<"MaxLength50">
type NonEmptyString50 = typeof NonEmptyString50.Type;
// SQLite supports JSON-compatible values.
const Person = object({
name: NonEmptyString50,
// Did you know that JSON.stringify converts NaN (a number) into null?
// Now, imagine that `age` accidentally becomes null. To prevent this, use FiniteNumber.
age: FiniteNumber,
});
type Person = typeof Person.Type;
// SQLite stores JSON-compatible values as strings. Fortunately, Evolu provides
// a convenient `json` Type Factory for type-safe JSON serialization and parsing.
const PersonJson = json(Person, "PersonJson");
// string & Brand<"PersonJson">
type PersonJson = typeof PersonJson.Type;
const Schema = {
todo: {
id: TodoId,
title: NonEmptyString1000,
// SQLite doesn't support the boolean type; it uses 0 (false) and 1 (true) instead.
// SqliteBoolean provides seamless conversion.
isCompleted: nullOr(SqliteBoolean),
categoryId: nullOr(TodoCategoryId),
personJson: nullOr(PersonJson),
},
todoCategory: {
id: TodoCategoryId,
name: NonEmptyString50,
},
};
Branded types allow TypeScript to enforce constraints like max length or non-empty values.
Create Evolu Instance
After defining the schema, create an Evolu instance for your environment.
import { createEvolu, getOrThrow, SimpleName } from "@evolu/common";
import { createUseEvolu, EvoluProvider } from "@evolu/react";
import { evoluReactWebDeps } from "@evolu/react-web";
const evolu = createEvolu(evoluReactWebDeps)(Schema, {
name: getOrThrow(SimpleName.from("your-app-name")),
syncUrl: "https://your-sync-url", // optional, defaults to https://free.evoluhq.com
});
// Wrap your app with <EvoluProvider>
<EvoluProvider value={evolu}>
{/* ... */}
</EvoluProvider>
// Create a typed React Hook returning an instance of Evolu
const useEvolu = createUseEvolu(evolu);
// Use the Hook in your app
const { insert, update } = useEvolu();
Mutate data
const { insert, update } = useEvolu();
const result = insert("todo", { title: "New Todo", isCompleted: false });
if (result.ok) {
update("todo", { id: result.value.id, isCompleted: true });
}
To delete a row, set isDeleted
to true and filter it out in your queries.
Evolu does not delete data—it marks them as deleted to preserve mergeability and support time travel.
Query data
Evolu uses type-safe TypeScript SQL query builder Kysely, so autocompletion works out-of-the-box.
Let's start with a simple Query
.
const allTodos = evolu.createQuery((db) => db.selectFrom("todo").selectAll());
Once we have a query, we can load or subscribe to it.
import { useQuery } from "@evolu/react";
// ...
const todos = useQuery(allTodos);
Protect data
Privacy is essential for Evolu, so all data are encrypted with an encryption key derived from a safely generated cryptographically strong password called mnemonic.
import { useAppOwner } from "@evolu/react";
// ...
const owner = useAppOwner();
if (owner) console.log(owner.mnemonic);
// this will print the mnemonic in the console
Delete data
To clear all local data from the device:
evolu.resetAppOwner();
Restore data
To restore synced data on any device:
evolu.restoreAppOwner(mnemonic);
That’s it. Evolu offers a minimal API designed for a great developer experience.