Example: To-do app
A full to-do feature: sign in, create/toggle/delete encrypted todos, and stay in sync across the user’s devices via the change feed. Plain TypeScript + minimal React — drop the data layer into any framework.
The data layer
Section titled “The data layer”import { Client } from "@muhkoo/connect";
export const client = new Client({ apiKey: "mk_live_pk_…", // baseUrl defaults to the hosted accelerator (https://api.muhkoo.dev).});
export interface Todo { title: string; done: boolean; createdAt: number;}
const COLLECTION = "todos";
export async function listTodos(): Promise<Array<{ id: string } & Todo>> { const ids = await client.kv.list(COLLECTION); const items = await Promise.all( ids.map(async (id) => { const t = await client.kv.get<Todo>(COLLECTION, id); return t ? { id, ...t } : null; }), ); return items .filter((t): t is { id: string } & Todo => t !== null) .sort((a, b) => a.createdAt - b.createdAt);}
export async function addTodo(title: string): Promise<void> { const id = crypto.randomUUID(); await client.kv.set<Todo>(COLLECTION, id, { title, done: false, createdAt: Date.now(), });}
export async function toggleTodo(id: string): Promise<void> { const t = await client.kv.get<Todo>(COLLECTION, id); if (!t) return; await client.kv.set<Todo>(COLLECTION, id, { ...t, done: !t.done });}
export async function removeTodo(id: string): Promise<void> { await client.kv.delete(COLLECTION, id);}The React component
Section titled “The React component”import { useEffect, useState } from "react";import { client, listTodos, addTodo, toggleTodo, removeTodo, type Todo } from "./todos";
export function Todos() { const [todos, setTodos] = useState<Array<{ id: string } & Todo>>([]); const [draft, setDraft] = useState("");
const refresh = () => listTodos().then(setTodos);
useEffect(() => { refresh(); // Live-update when todos change on any of the user's devices. const off = client.kv.on("change", (e) => { if (e.collection === "todos") refresh(); }); return off; }, []);
return ( <section> <form onSubmit={async (e) => { e.preventDefault(); if (!draft.trim()) return; await addTodo(draft.trim()); setDraft(""); refresh(); }} > <input value={draft} onChange={(e) => setDraft(e.target.value)} placeholder="New todo…" /> <button type="submit">Add</button> </form>
<ul> {todos.map((t) => ( <li key={t.id}> <label style={{ textDecoration: t.done ? "line-through" : "none" }}> <input type="checkbox" checked={t.done} onChange={() => toggleTodo(t.id).then(refresh)} /> {t.title} </label> <button onClick={() => removeTodo(t.id).then(refresh)}>✕</button> </li> ))} </ul> </section> );}Signing in first
Section titled “Signing in first”The component assumes a signed-in user. Wire login wherever your auth UI lives:
await client.auth.zk.login(username, password);// …then mount <Todos />What’s happening
Section titled “What’s happening”- Every todo is encrypted on the device before storage — the server stores ciphertext keyed by a random id.
storage.on('change')keeps every open tab/device in sync with no polling.- There’s no server-side query, so we
list()ids and fetch each. For a big list, keep a small plaintext index (see Storage → Querying).