# Scaffold with create-rindle

Generate a SQL-first Rindle app on TanStack Start: browser client, API authority, rindled daemon, migrations, generated schema, SSR, and devtools in one template.

The [synced-app quickstart](/docs/synced-app-quickstart) builds a Rindle app by
hand so you can see every seam. `create-rindle` is the shortcut after that: it
generates the same three-tier shape as a small, running [TanStack Start](https://tanstack.com/start)
app.

Use it when you want a project to edit instead of copy-pasting the quickstart.
The template is intentionally small - a rooms-and-messages app with live message
counts, optimistic writes, a rejection path, [SSR](/docs/ssr), generated schema,
and [dev-only Rindle devtools](/docs/devtools).

## Create an app

```bash
npm create rindle@latest my-app
# or
pnpm create rindle my-app
# or
npx create-rindle my-app
```

Then run it:

```bash
cd my-app
pnpm dev
```

`pnpm dev` uses [`concurrently`](https://www.npmjs.com/package/concurrently) to run
two processes — the standard TanStack Start dev shape: a **Rindle** process
(`rindle up` rendering the committed `rindle.ncl` and supervising the write-master +
follower pair, applying migrations to the master and regenerating `shared/schema.gen.ts`
from the follower on every `migrations/*.sql` change) and a **web** process (`vite dev`
on port 3000, serving the app and the `/api/rindle/*` server routes). It sets
`RINDLE_REPLICATOR_URL` so the API authority's write leg reaches the master. Run either
alone with `pnpm fleet` / `pnpm dev:web`.

Open two browser windows, create a room, and the room count updates live in both as
you post messages. Try a room name or message containing `"spam"` to watch an
optimistic write snap back after the API authority rejects it.

> The local pair binds to loopback and runs without auth tokens; a real deployment
> sets them (tokens on the write-master and follower plus a verified
> `RINDLE_DAEMON_TOKEN` / `RINDLE_REPLICATOR_TOKEN` on the server), which never reach
> the browser.

> The generated app requires Node >= 22.18.

## What it generates

It is the same architecture as the quickstart, packaged as a starter:

| Tier | In the template | What it does |
| --- | --- | --- |
| Browser | `src/rindle-client.ts`, `src/components/*.queries.ts`, `src/routes/*` | Runs `createRindleClient`, local wasm IVM, optimistic writes, `useRoot`/`useFragment`, and co-located queries/fragments |
| API authority | `server/app-api.ts`, `server/rindle-http.ts`, `src/routes/api.rindle.*.tsx` | Resolves named queries, runs authoritative SQL mutators (a `SplitDaemonClient`: writes → the write-master, reads → the follower), and exposes the Rindle API through TanStack Start server routes |
| Data tier | `rindle.ncl`, `migrations/*.sql` | `rindle up` renders `rindle.ncl` and supervises the write-master + follower pair, applies migrations to the master, generates schema from the follower, and streams live deltas |

The schema is SQL-first. Edit or add `migrations/*.sql`; the dev loop applies the
migration to the write-master and regenerates `shared/schema.gen.ts` from the follower's
introspected schema. Keep relationships, normalization, and mutators in
`shared/app-def.ts`.

## Why TanStack Start

The starter uses TanStack Start because it gives the template one coherent home
for the browser, SSR, and server routes:

- `src/routes/api.rindle.query.tsx`, `api.rindle.read.tsx`, and
  `api.rindle.mutate.tsx` are the browser-facing API routes.
- [SSR reads](/docs/ssr) call the same app authority in-process, so first paint
  and client subscriptions use the same query registry.
- TanStack Router's file routes keep app screens and Rindle query modules close
  without forcing the Rindle APIs themselves to depend on TanStack.

Rindle is not TanStack-specific. The important parts are the three tiers and the
contracts: SQL migrations, generated schema, named queries, predicted mutators in
the browser, authoritative mutators on the server, and the write-master + follower as
the live data tier. TanStack Start is just the starter's application shell.

## Where to look first

| File | Why it matters |
| --- | --- |
| `migrations/0001_init.sql` | The source-of-truth SQL schema |
| `shared/schema.gen.ts` | Generated `@rindle/client` schema; do not hand-edit |
| `shared/app-def.ts` | Schema re-export, relationships, normalization, and predicted mutators |
| `src/components/*.queries.ts` | Named root queries and fragments, co-located with UI |
| `src/rindle-client.ts` | The one browser client setup call |
| `src/ssr.ts` | Server-only first-paint preload helper |
| `src/devtools.tsx` | Dev-only Rindle devtools panel mount |
| `server/app-api.ts` | Authoritative query registry, SQL mutators, policy |
| `rindle.ncl` | The topology `rindle up` (local) and `rindle deploy` (cloud) both read — `followers = 1`, the colocated pair; loopback, no auth token in dev |
| `AGENTS.md` | The rules of the app for coding agents (never edit `schema.gen.ts`, deterministic mutators, named queries, …) — Claude Code, Cursor, and Codex read it automatically |

## Customize it

Treat the generated app as a working baseline:

- change tables in `migrations/*.sql`, then let `pnpm dev` regenerate the typed
  schema;
- add relationships and mutators in `shared/app-def.ts`;
- add named root queries or fragments beside the components that read them;
- replace the demo auth seam in `shared/auth.ts` and the policy in
  `server/app-api.ts`;
- keep the daemon token server-only if you point the app at Rindle Cloud or a
  self-hosted daemon.

The same `rindle.ncl` drives the cloud: `rindle login`, then `pnpm rindle:deploy`
provisions (or re-attaches) the managed write-master + follower and records the binding
in `.rindle/cloud.json` (commit it), and `pnpm rindle:migrate:remote` pushes
`migrations/*.sql` to the write-master. Local dev reads the same file — `rindle up`
renders it to the colocated pair. See [deploy](/docs/deploy).

For the underlying commands, see [`@rindle/cli`](/docs/rindle-cli). For the
manual version of the same app shape, keep the [quickstart](/docs/synced-app-quickstart)
open beside the generated project.

## Next steps

- [Synced-app quickstart](/docs/synced-app-quickstart) - the same architecture,
  built by hand.
- [The three-tier architecture](/docs/architecture) - why the browser, API
  authority, and daemon are separated.
- [`@rindle/cli`](/docs/rindle-cli) - the `rindle` toolchain the template uses
  for the daemon, migrations, and schema generation.
- [The browser client](/docs/client) - `createRindleClient`, optimistic mutators,
  and live reads.
- [Server rendering](/docs/ssr) - the first-paint preload and live-store handoff.
- [Devtools](/docs/devtools) - the dev-only mutation timeline, query inspector,
  and delta stream.

---

[View this page on Rindle](https://rindle.sh/docs/create-rindle)
