Building apps

Local development

What railcode dev runs locally, what it forwards to your real instance, and the limits you accept.

railcode dev [--port <n>] [--reset]

Run it from the directory that contains railcode.json. It serves the frontend and the backend function. It routes the same paths that production routes (/api/*, /_serverFn/*).

  • tanstack: the Vite preset runs the backend function in an embedded runtime, and the CLI proxies /api and /_serverFn to Vite.
  • hono+vite / hono+static / bring-your-own: the CLI bundles the backend function with esbuild (watched), runs it in-process, and serves the same paths. A rebuild re-imports the backend function on the next request.

Either way, the backend function calls the CLI's local data plane over HTTP with the same wire format as production. So "works in railcode dev" means "works deployed."

Local vs forwarded

SurfaceUnder railcode dev
db, filesLocal scratch store on disk. --reset seeds it fresh. Never touches live data
SQL, saved queries, LLM, email, connectors, agentsForwarded to the real instance under a CLI-minted dev token that carries your identity
secretsRead from your local environment
CronNot scheduled. Trigger the route by hand (remember: POST)

Forwarded calls hit real providers, real data, and real spend, including sending email and starting agent runs. Their authority is bounded by the manifest exactly as in production, so authority failures reproduce locally.

Accepted limits

A single identity, secrets from your local environment, and cron triggered by hand.

Local storage is separate

Local dev KV and files live under ~/.railcode/dev/<instance>/<app>/. Only railcode dev --reset clears them.

railcode app kv and railcode app files read and write the live app, never the local emulation. So under railcode dev, seed data through the app's own UI instead.

Validating before you ship

railcode dev                # confirm the frontend loads and its /api routes answer
railcode manifest validate  # strict local parse of manifest.yaml

manifest validate parses the file with the same strict YAML grammar the server uses. Use spaces, not tabs. A bare yes / no / on / off / null / number resolves to a non-string and is rejected where a name is expected. Resource names are only checked against your org at deploy time.

Then exercise the app end to end at desktop and mobile widths. Treat console errors, failed /api/* calls, and broken layouts as failures to fix.

On this page