Quickstart: React
Build a real-time todo app with Vite + React in about 5 minutes.
Prerequisites: Node.js 20+ (or Bun)
-
Create a React app
Terminal window npm create vite@latest my-todo -- --template react-tscd my-todoTerminal window bun create vite my-todo --template react-tscd my-todoTerminal window pnpm create vite my-todo --template react-tscd my-todoTerminal window yarn create vite my-todo --template react-tscd my-todo -
Set up Zeroback
Terminal window npx @zeroback/cli initThis scaffolds a
zeroback/directory with a starter schema and todo functions. -
Install dependencies
Terminal window npm install @zeroback/server @zeroback/reactTerminal window bun add @zeroback/server @zeroback/reactTerminal window pnpm add @zeroback/server @zeroback/reactTerminal window yarn add @zeroback/server @zeroback/react -
Start the Zeroback dev server
Terminal window npx @zeroback/cli devThis starts a local Cloudflare Worker and generates typed APIs into
zeroback/_generated/. Keep this running. -
Wire up the React client
In a second terminal, replace
src/App.tsx:import { ZerobackClient, ZerobackProvider, useQuery, useMutation } from "@zeroback/react"import { api } from "../zeroback/_generated/api"import { useState } from "react"const client = new ZerobackClient("ws://localhost:8788/ws")function TodoApp() {const tasks = useQuery(api.tasks.list)const createTask = useMutation(api.tasks.create)const toggleTask = useMutation(api.tasks.toggle)const [text, setText] = useState("")const handleAdd = async (e: React.FormEvent) => {e.preventDefault()if (!text.trim()) returnawait createTask({ text: text.trim() })setText("")}return (<div style={{ maxWidth: 500, margin: "2rem auto", fontFamily: "sans-serif" }}><h1>Todos</h1><form onSubmit={handleAdd} style={{ display: "flex", gap: 8 }}><inputvalue={text}onChange={(e) => setText(e.target.value)}placeholder="What needs to be done?"style={{ flex: 1, padding: 8 }}/><button type="submit" style={{ padding: "8px 16px" }}>Add</button></form><ul style={{ listStyle: "none", padding: 0, marginTop: 16 }}>{tasks === undefined ? (<li>Loading...</li>) : (tasks.map((task) => (<likey={task._id}onClick={() => toggleTask({ id: task._id })}style={{padding: "8px 0",cursor: "pointer",textDecoration: task.isCompleted ? "line-through" : "none",opacity: task.isCompleted ? 0.5 : 1,}}>{task.isCompleted ? "☑" : "☐"} {task.text}</li>)))}</ul></div>)}export default function App() {return (<ZerobackProvider client={client}><TodoApp /></ZerobackProvider>)} -
Start the frontend
Terminal window npm run devOpen http://localhost:5173 and start adding tasks.
What just happened?
Section titled “What just happened?”zeroback initscaffolded azeroback/directory with aschema.ts(tasks table) andtasks.ts(list, create, and toggle functions).zeroback devstarted a local Cloudflare Worker with a Durable Object backed by SQLite. It watches yourzeroback/directory and regenerates types on every change.useQuerysubscribes totasks.listover WebSocket. When any client adds or toggles a task, every connected browser updates instantly — no polling, no refetching.useMutationcallstasks.createandtasks.toggleon the server. Mutations run in a transaction and trigger reactive updates to all subscribers.
Try it: real-time
Section titled “Try it: real-time”Open the app in two browser tabs. Add a task in one — it appears in the other instantly. Toggle it complete — both tabs update.
Next steps
Section titled “Next steps”- Schema — Indexes, validators, and search indexes
- Functions — Actions, HTTP routes, cron jobs
- Database — Filters, pagination, full-text search
- React hooks —
usePaginatedQuery, optimistic updates, connection state - Deployment — Deploy to Cloudflare