Skip to content

Quickstart: React

Build a real-time todo app with Vite + React in about 5 minutes.

Prerequisites: Node.js 20+ (or Bun)

  1. Create a React app

    Terminal window
    npm create vite@latest my-todo -- --template react-ts
    cd my-todo
  2. Set up Zeroback

    Terminal window
    npx @zeroback/cli init

    This scaffolds a zeroback/ directory with a starter schema and todo functions.

  3. Install dependencies

    Terminal window
    npm install @zeroback/server @zeroback/react
  4. Start the Zeroback dev server

    Terminal window
    npx @zeroback/cli dev

    This starts a local Cloudflare Worker and generates typed APIs into zeroback/_generated/. Keep this running.

  5. 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()) return
    await 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 }}>
    <input
    value={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) => (
    <li
    key={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>
    )
    }
  6. Start the frontend

    Terminal window
    npm run dev

    Open http://localhost:5173 and start adding tasks.

  • zeroback init scaffolded a zeroback/ directory with a schema.ts (tasks table) and tasks.ts (list, create, and toggle functions).
  • zeroback dev started a local Cloudflare Worker with a Durable Object backed by SQLite. It watches your zeroback/ directory and regenerates types on every change.
  • useQuery subscribes to tasks.list over WebSocket. When any client adds or toggles a task, every connected browser updates instantly — no polling, no refetching.
  • useMutation calls tasks.create and tasks.toggle on the server. Mutations run in a transaction and trigger reactive updates to all subscribers.

Open the app in two browser tabs. Add a task in one — it appears in the other instantly. Toggle it complete — both tabs update.

  • Schema — Indexes, validators, and search indexes
  • Functions — Actions, HTTP routes, cron jobs
  • Database — Filters, pagination, full-text search
  • React hooksusePaginatedQuery, optimistic updates, connection state
  • Deployment — Deploy to Cloudflare