1. Project setup
This tutorial builds Desksmith — a small storefront with a product catalog, an add-to-cart flow, a cart page, a client-only theme toggle, persistence, and live updates. Each page adds one capability, and every step maps to real, working patterns from the apps/example app in the Stator repository.
By the end you’ll have touched every distinctive part of Stator: machines, server rendering with read(), events, a client island, the store, and SSE.
What we’re building
Section titled “What we’re building”A catalog of desk goods grouped into categories, with an “Add to cart” button on each product, a cart page that lets you change quantities, and a theme toggle that lives entirely in the browser. We’ll start with the catalog and grow outward.
The shape of a Stator app
Section titled “The shape of a Stator app”Desksmith follows the standard layout — state in machines/, pages in routes/, reusable components in templates/:
desksmith/├── machines/│ ├── products.ts # the catalog (app-lifecycle)│ └── cart.ts # the cart (session-lifecycle)├── routes/│ ├── index.stator # the catalog page│ └── cart.stator # the cart page├── templates/│ ├── base-layout.stator│ ├── customer-layout.stator│ ├── product-list.stator│ └── theme-toggle.stator├── static/├── server.ts└── sync.tsThis is the end state — what you’ll have built by the last step. Create each file when the step that introduces it tells you to; don’t pre-create them now.
Prerequisites & scaffold
Section titled “Prerequisites & scaffold”Follow Installation first: install @statorjs/stator, tsx, and typescript, and create the server.ts and sync.ts entry points shown there. Add "type": "module" and the script aliases to package.json:
{ "type": "module", "scripts": { "sync": "tsx sync.ts", "dev": "tsx watch server.ts" }}"type": "module" is required — the entry points use top-level await, which needs ES modules.
Configure TypeScript
Section titled “Configure TypeScript”Stator generates per-component type declarations into .stator/types/ so your editor and tsc can type-check component props. A few settings make that work — rootDirs (so the generated declarations resolve alongside your source), allowImportingTsExtensions (the code imports machines with explicit .ts paths), jsx: preserve (the .stator template body is JSX), and including **/*.stator (so the editor language server applies these options to your components rather than TS defaults):
{ "compilerOptions": { "target": "ES2022", "module": "ESNext", "moduleResolution": "Bundler", "lib": ["ES2022", "DOM"], "strict": true, "jsx": "preserve", "allowImportingTsExtensions": true, "noEmit": true, "rootDirs": [".", ".stator/types"] }, "include": ["**/*.ts", "**/*.stator"], "exclude": ["node_modules", "static", "dist"]}tsc ignores the **/*.stator glob (it only compiles the generated .ts), but the language server uses it to give your .stator files completions and type errors with the same options as the rest of the project.
The .stator/ directory is generated, so add it to .gitignore:
.stator/dist/Now install dependencies and generate the type declarations for the first time:
npm installpnpm installyarn installnpm run syncpnpm run syncyarn run syncRe-run sync whenever a component’s props change.
The dev server
Section titled “The dev server”Your server.ts boots the dev server and points it at the three directories. For the tutorial, the in-memory store is fine — we’ll swap it for Redis in step 7:
import { resolve, dirname } from 'node:path'import { fileURLToPath } from 'node:url'import { InMemoryStore } from '@statorjs/stator/server'import { createDevApp } from '@statorjs/stator/dev'
const here = dirname(fileURLToPath(import.meta.url))
const app = await createDevApp({ root: here, machinesDir: resolve(here, 'machines'), routesDir: resolve(here, 'routes'), staticDir: resolve(here, 'static'), store: new InMemoryStore(),})
await app.listen(Number(process.env.PORT ?? 3000))During development, Vite compiles .stator files as they’re requested — there’s no separate build step to run while you work.
Run it
Section titled “Run it”npm run devpnpm run devyarn run devVisit http://localhost:3000. You’ll get a 404 until we add a route — that’s next.
What you built · next
Section titled “What you built · next”A project skeleton with a running dev server. In step 2 we define the two machines that hold Desksmith’s state.