Getting Started
Quick Start
Build a sync-enabled todo app in 5 minutes
Quick Start
Let's build a simple todo backend with RippleDB. By the end, you'll have a server that:
- Stores changes in an append-only log
- Materializes changes into a
todostable - Supports cursor-based sync for clients
Prerequisites
- Node.js 18+
- A new project directory
Install Dependencies
mkdir rippledb-demo && cd rippledb-demo
npm init -y
npm install @rippledb/core @rippledb/db-sqlite @rippledb/materialize-db better-sqlite3
npm install -D typescript @types/better-sqlite3 tsxDefine Your Schema
Create src/schema.ts to define the shape of your data:
import { defineSchema, s, type InferSchema } from "@rippledb/core";
export const schema = defineSchema({
todos: {
id: s.string(),
title: s.string(),
done: s.boolean(),
},
});
export type TodosSchema = InferSchema<typeof schema>;Set Up the Database
Create src/db.ts to configure your RippleDB database:
import { SqliteDb } from "@rippledb/db-sqlite";
import { createSyncMaterializer } from "@rippledb/materialize-db";
import type { TodosSchema } from "./schema";
import { schema } from "./schema";
// Create the database with a materializer
export const db = new SqliteDb<TodosSchema>({
filename: "./data.db",
schema,
materializer: ({ db, schema }) =>
createSyncMaterializer({
schema,
db,
dialect: "sqlite",
tableMap: { todos: "todos" },
fieldMap: { todos: { id: "id", title: "title", done: "done" } },
}),
});Create HLC Generator
Create src/hlc.ts to generate timestamps:
import { createHlcState, tickHlc } from "@rippledb/core";
// Each node needs a unique ID
const nodeId = `server-${Math.random().toString(36).slice(2, 8)}`;
const hlcState = createHlcState(nodeId);
export function hlc() {
return tickHlc(hlcState, Date.now());
}Write Your First Change
Create src/index.ts:
import { makeUpsert, makeDelete } from "@rippledb/core";
import { db } from "./db";
import { hlc } from "./hlc";
import type { TodosSchema } from "./schema";
async function main() {
const stream = "user-123"; // Partition key (e.g., user ID)
// Create a todo
const createTodo = makeUpsert<TodosSchema>({
stream,
entity: "todos",
entityId: "todo-1",
patch: { id: "todo-1", title: "Buy milk", done: false },
hlc: hlc(),
});
await db.append({ stream, changes: [createTodo] });
console.log("Created todo:", createTodo);
// Update the todo
const updateTodo = makeUpsert<TodosSchema>({
stream,
entity: "todos",
entityId: "todo-1",
patch: { done: true },
hlc: hlc(),
});
await db.append({ stream, changes: [updateTodo] });
console.log("Updated todo:", updateTodo);
// Pull all changes
const { changes, nextCursor } = await db.pull({ stream, cursor: null });
console.log("All changes:", changes);
console.log("Cursor for next pull:", nextCursor);
// Delete the todo
const deleteTodo = makeDelete<TodosSchema>({
stream,
entity: "todos",
entityId: "todo-1",
hlc: hlc(),
});
await db.append({ stream, changes: [deleteTodo] });
console.log("Deleted todo");
db.close();
}
main();Run It
npx tsx src/index.tsYou should see output like:
Created todo: { stream: 'user-123', entity: 'todos', entityId: 'todo-1', kind: 'upsert', ... }
Updated todo: { stream: 'user-123', entity: 'todos', entityId: 'todo-1', kind: 'upsert', ... }
All changes: [{ ... }, { ... }]
Cursor for next pull: 2
Deleted todoWhat Just Happened?
-
makeUpsertcreated a Change object with:kind: 'upsert'— insert or update semanticspatch— the fields being changedtags— auto-generated HLC timestamps per fieldhlc— the change-level timestamp
-
db.appendstored the change and materialized it:- Appended to the
ripple_changestable - Applied field-level LWW to the
todostable - Updated the
ripple_tagstable with field timestamps
- Appended to the
-
db.pullreturned all changes since cursornull(the beginning). -
makeDeletemarked the entity as deleted using a tombstone.
Next Steps
Now that you understand the basics, dive deeper into the core concepts.
- Core Concepts — Understand HLCs, streams, and field-level LWW
- Server Setup Guide — Build a production-ready sync server
- Adapters — Choose the right database for your use case