Get started with local-first

This guide will get you all set up and ready to use Evolu.

Choose a platform
This selection will apply to all code examples on this page.

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 @evolu/react @evolu/react-web

Define schema

First, define your app 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.

import * as Evolu from "@evolu/common";

// Primary keys are branded types, preventing accidental use of IDs across
// different tables (e.g., a TodoId can't be used where a UserId is expected).
const TodoId = Evolu.id("Todo");
type TodoId = typeof TodoId.Type;

// Schema defines database structure with runtime validation.
// Column types validate data on insert/update/upsert.
const Schema = {
  todo: {
    id: TodoId,
    // Branded type ensuring titles are non-empty and ≤100 chars.
    title: Evolu.NonEmptyString100,
    // SQLite doesn't support the boolean type; it uses 0 and 1 instead.
    isCompleted: Evolu.nullOr(Evolu.SqliteBoolean),
  },
};

Create Evolu

After defining the schema, create an Evolu instance for your environment.

import { createEvolu, SimpleName } from "@evolu/common";
import { createUseEvolu, EvoluProvider } from "@evolu/react";
import { evoluReactWebDeps } from "@evolu/react-web";

const evolu = createEvolu(evoluReactWebDeps)(Schema, {
  name: SimpleName.orThrow("your-app-name"),
  transports: [{ type: "WebSocket", url: "wss://your-sync-url" }], // optional, defaults to 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: Evolu.sqliteFalse,
});

if (result.ok) {
  update("todo", { id: result.value.id, isCompleted: Evolu.sqliteTrue });
}

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

Delete data

To delete a row, set isDeleted to sqliteTrue (1). Evolu uses soft deletes instead of permanent deletion.

const { update } = useEvolu();

// Mark a todo as deleted
update("todo", { id: todoId, isDeleted: Evolu.sqliteTrue });

When querying, filter out deleted rows:

const activeTodos = evolu.createQuery((db) =>
  db
    .selectFrom("todo")
    .selectAll()
    // Filter out deleted rows
    .where("isDeleted", "is not", Evolu.sqliteTrue)
    .orderBy("createdAt"),
);

Protect data

Privacy is essential for Evolu, so all data are encrypted with an encryption key derived from a cryptographically strong secret (which can be represented as a mnemonic) or provided by an external hardware device.

import { use } from "react";
// ...
const evolu = useEvolu();
const owner = use(evolu.appOwner);
console.log(owner.mnemonic);
// this will print the mnemonic in the console

Purge data

To clear all local data from the device (this is different from soft deletes):

evolu.resetAppOwner();

Restore data

To restore synced data on any device:

evolu.restoreAppOwner(mnemonic);

To learn more about Evolu, explore our playgrounds and examples.

Was this page helpful?