# How Helix works

> Where Helix code runs: a local Nitro dev server on localhost:3000, one AWS Lambda per project in production, and shared platform services.

A Helix project is a set of TypeScript functions plus an optional React SPA, and the same code runs in two places. During development the CLI serves everything from one local server on localhost:3000. In production each project runs as a single AWS Lambda behind `https://{project-id}.helix-app.ai`, and both runtimes call the same platform services: auth proxy, AI gateway, scheduling, MCP, and managed data stores.

## Local development

`helix dev` starts a Nitro dev server on localhost:3000. It serves three kinds of traffic from one origin:

- Function routes go to the Nitro function runtime, which maps file paths in `functions/` to URLs.
- Static assets are served by Vite, integrated as a Nitro plugin.
- Everything else falls back to the SPA's `index.html`.

Because functions and the SPA share an origin, there is no CORS configuration in development. The deployed project keeps the same single-origin layout, so there is none in production either.

The CLI also starts local backing services automatically, no flags needed. Database, key-value store, file storage, scheduler, and log output are always local:

```
helix dev (localhost:3000)
  |
  |-- Function routes  -> Nitro function runtime
  |-- Static assets    -> Vite (Nitro plugin)
  |-- SPA fallback     -> index.html
  |
  |-- Local Postgres       <- ctx.db()    (always local)
  |-- Local KV, in memory  <- ctx.kv()    (always local)
  |-- Local file system    <- ctx.files   (always local)
  |-- Local scheduler      -> runs functions/_scheduled/ on their cron
  |
  +-- Remote auth service  <- ctx.http.authed()  (always remote)
```

The one exception is authentication. `ctx.http.authed('alias')` always resolves against the remote Helix auth service, even during local development, because credentials are never stored on your machine. To work without a network connection, `helix dev --offline` returns configurable mock responses instead of calling the auth service.

## Production

`helix deploy` builds the project and ships it to the platform. The build bundles every function into a single Lambda-compatible entry point with an internal router, compiles the SPA with `vite build` if `app/` exists, and generates a manifest describing routes, schedules, and schemas. The deploy then:

1. Uploads the function bundle to AWS Lambda, one Lambda per project.
2. Uploads SPA assets to the project's S3 bucket, served through CloudFront.
3. Sends the Drizzle schema to the migration API. The CLI never connects to the production database directly.
4. Registers schedules and app triggers with the platform.
5. Updates API Gateway routing and switches traffic to the new version.

All production traffic arrives at CloudFront, which routes by match priority:

```
{project-id}.helix-app.ai
        |
   CloudFront CDN
        |
        |-- 1. Function route match -> API Gateway -> project Lambda
        |-- 2. Static asset match   -> S3 (cached at the edge)
        +-- 3. SPA fallback         -> index.html from S3
```

Nitro exists only in development; the production runtime is AWS Lambda. Functions scale through Lambda concurrency, scale to zero when idle, and each project's concurrency cap keeps one project from consuming unbounded resources. Custom domains can be added in the dashboard and serve the same project alongside the UUID subdomain; see the [deployment guide](/documentation/guides/deployment/).

:::roadmap
On-premise deployment, with a Kubernetes-based runtime replacing Lambda and the data stores and auth proxy running inside your own infrastructure, is planned but not yet available. The project structure and the `helix deploy` command are designed to stay identical when it arrives.
:::

## Platform services

Five services sit behind both runtimes:

| Service | What it does |
|---|---|
| Auth proxy | Resolves auth aliases to credential records, injects credentials server-side, and logs every authenticated call. See the [security model](/documentation/concepts/security-model/). |
| AI gateway | One API for OpenAI, Anthropic, and Google models, with per-request cost tracking and org-level budgets, model rules, and rate limits. |
| Scheduling service | Triggers `functions/_scheduled/` functions on their cron schedules in production. A local scheduler does the same during `helix dev`. |
| MCP service | Exposes the functions listed in your config's `mcp.tools` as MCP tools at `mcp+https://{project-id}.mcp.helix.ai`. |
| Managed data stores | Postgres with pgvector, key-value store, queues, and S3-backed file storage, provisioned per project on deploy. |

## The life of a request

Here is what happens when a browser calls `GET https://{project-id}.helix-app.ai/api/v1/orders` on a deployed project:

1. CloudFront receives the request and checks it against the deployed route table. `/api/v1/orders` matches a function route.
2. If Helix Identity is enabled for the project, the Helix edge checks the session cookie first and redirects to your IdP when there is no valid session.
3. API Gateway forwards the request to the project's Lambda, where the internal router selects the handler compiled from `functions/api/v1/orders.get.ts`.
4. Middleware runs before the handler: org-level middleware first, if your organization defines any, then the project's `functions/_middleware.ts`.
5. The runtime validates the merged input (body, params, and query) against the function's exported Zod schema, then calls the handler with `ctx`.
6. Inside the handler, `ctx.db()` queries the project's managed Postgres, and any `ctx.http.authed('alias')` call routes through the auth proxy, which injects credentials and forwards the request.
7. The handler's return value becomes the response and travels back through API Gateway and CloudFront to the browser.
8. Every step is recorded under one execution ID, which you can trace in the dashboard at `https://app.helix.tray.ai`.

A request for a static asset such as `/assets/main-abc123.js` never reaches the Lambda: CloudFront serves it from the S3 cache at step one.

---

Canonical: https://helix.tray.ai/documentation/concepts/how-helix-works/
Any link on this page is available as markdown by appending .md to its URL.
Full corpus: https://helix.tray.ai/documentation/llms-full.txt