Skip to content

7. Persisting state

The cart already survives navigation — click around and it stays full. That’s the store doing its job. This step explains how, and how to swap the in-memory store for Redis so the cart also survives a server restart.

A lifecycle: 'session' machine belongs to one visitor, identified by a session cookie Stator sets automatically. Between requests, the cart isn’t a live object sitting in memory — it’s a snapshot in the store, keyed by session. Each request rehydrates the machines it needs, runs the event, and writes the touched ones back. (App-lifecycle machines like the catalog are the exception — they live in the process and aren’t per-session.)

This is why the runtime stays cheap: there’s no long-lived actor per visitor, just snapshots that load and save around each request.

A store is anything implementing four methods:

interface Store {
get(sessionId: string, machineName: string): Promise<unknown | null>
set(sessionId: string, machineName: string, snapshot: unknown, ttlSeconds: number): Promise<void>
has(sessionId: string, machineName: string): Promise<boolean>
deleteSession(sessionId: string): Promise<void>
}

Snapshots are opaque JSON — the store never needs to understand your machine, just persist its serialized form. TTL is per session, not per machine, so a whole cart expires together rather than losing line items mid-checkout.

InMemoryStore (what we’ve used so far) keeps snapshots in a Map. It’s perfect for development and zero-config, with one catch: state lives only as long as the process. Restart the dev server and every cart is gone. You’ll see a warning to that effect on boot, which is your reminder that it’s not for production.

Move to durable persistence by changing one line in server.ts. Stator ships RedisStore and a CachedStore wrapper that fronts Redis with an in-memory cache to cut command counts:

import {
InMemoryStore,
RedisStore,
CachedStore,
type Store,
} from '@statorjs/stator/server'
let store: Store
if (process.env.REDIS_URL) {
store = new CachedStore(new RedisStore(process.env.REDIS_URL), {
memoryTtlSeconds: 300,
maxEntries: 10_000,
})
} else {
store = new InMemoryStore()
}
const app = await createDevApp({
// …root, dirs…
store,
sessionTtlSeconds: 86_400, // 24h idle window, refreshed on each cart action
})

Nothing in your machines or templates changes — the store is a pure infrastructure swap. Set REDIS_URL and carts now survive restarts and deploys.

  • Session machines (the cart) — persisted to the store, survive restarts on Redis.
  • App machines (the catalog) — live in process memory; re-seeded on boot.

A cart that persists across requests and — on Redis — across restarts, with no change to your application code. In step 8 we make a page update live as state changes elsewhere.