helipod
Deploy & Operate

Vite plugin

Run your frontend and the helipod backend with one vite command, on one origin.

Normally a Vite app and a helipod backend are two terminals: vite in one, helipod dev in the other, plus a hand-written proxy so the browser can reach both. @helipod/vite collapses that to one command. You run vite, and the plugin brings the backend up with it, on the same browser origin.

One origin matters more than one terminal. The client connects to ${location.host}/api/sync, so when the engine answers on the same host and port as your frontend, there is no proxy config to write and no CORS to think about. It just works.

The plugin has two modes behind the same helipod() call:

  • Proxy mode (the default) spawns helipod dev as a child process and wires Vite's proxy to it. The backend behaves exactly as if you had started it yourself.
  • Embed mode boots the engine inside Vite's own process. No child process, no proxy hop: the engine answers as Vite middleware, and the reactive WebSocket shares Vite's HTTP server.

Set up

Install the plugin

@helipod/vite goes next to your existing dev dependencies. It needs vite 5 or newer, and it drives @helipod/cli (already a dev dependency if you followed the quickstart):

npm install -D @helipod/vite

Add it to vite.config.ts

vite.config.ts
import { defineConfig } from "vite";
import { helipod } from "@helipod/vite";

export default defineConfig({
  plugins: [helipod()],
});

That's the whole configuration. No options are required.

Run vite

npx vite

The backend starts with the dev server. Its log lines appear in the same terminal, prefixed [helipod]. Open your app at the Vite URL, and the engine's surfaces are there too, on the same origin:

  • /api/* is the engine's HTTP API, and /api/sync is the reactive WebSocket.
  • /_dashboard is the dashboard.
  • /_admin/* is the admin API the dashboard runs on.

Everything else is your Vite app, untouched.

The two modes

Proxy mode spawns helipod dev as a child process on a free port and injects three entries into Vite's dev-server proxy: /api (WebSocket-aware, so /api/sync upgrades pass through), /_dashboard, and /_admin. Vite merges these with any server.proxy entries of your own, so your unrelated proxy rules are preserved.

Because the child is the real helipod dev, everything on the local development page holds verbatim: codegen runs on start, the child watches helipod/ and hot-reloads your functions on every save, and the ephemeral admin key is printed in its (piped) output.

Some mechanics worth knowing:

  • CLI resolution. The plugin runs your app's local node_modules/.bin/helipod if it exists, and falls back to npx helipod. Override with the command option.
  • Readiness. Vite waits for the backend to accept TCP connections before serving (polled every 200ms, 30 second timeout). If the child exits early or never comes up, vite fails with a clear error instead of serving a dead proxy.
  • Cleanup. The child is killed when the Vite server closes and on SIGINT/SIGTERM/process exit, so a stopped vite never leaves an orphaned backend behind.
  • Extra flags. Anything helipod dev accepts can be forwarded through args, for example args: ["--database-url", "postgres://..."].

Options

All options are optional. mode and functionsDir apply to both modes; the rest belong to one mode and are ignored by the other.

Prop

Type

Hot reload and HMR

Two independent reload loops run side by side, and neither steps on the other:

  • Frontend HMR is Vite's, unchanged. Editing a component hot-swaps it in the browser exactly as in any Vite app.
  • Backend hot reload is helipod's. Saving a file under helipod/ reloads your schema and functions and regenerates helipod/_generated/, without dropping live subscriptions. A client watching a query stays connected and re-runs against the new code on the next invalidation. A broken save logs ✗ reload failed and keeps the previous working functions running.

In proxy mode, the child's own file watcher does this, exactly as described under local development. In embed mode, the plugin subscribes to Vite's own file watcher instead: changes under functionsDir (except _generated/) are debounced for 50ms and then re-pushed in-process.

Regenerated _generated/ files are ordinary source files as far as Vite is concerned, so a codegen change that your frontend imports (a new function on api, say) flows into the browser through Vite's normal HMR.

Limits

  • The plugin is dev only. It shapes vite's dev server and nothing else. vite build produces your frontend as usual, and you deploy the backend separately (self-hosting, deploy and build).
  • Embed mode caps request bodies at 5 MiB on the engine paths. A larger file storage upload through /api/storage/upload won't fit; use proxy mode, or an S3-backed presigned upload, for big files.
  • Embed mode needs Vite's own HTTP server. Under server.middlewareMode (how some SSR frameworks host Vite) there is no server to attach to, so the /api/sync WebSocket never wires up and engine cleanup doesn't run. The plugin warns loudly; use proxy mode there.
  • Proxy mode is two processes. That's by design (zero divergence from the real helipod dev), but it means backend state lives in the child. If you need to poke the engine from Vite's process, that's what embed mode is for.
  • Local development: everything the backend does under the plugin, flags, hot reload, and the dashboard.
  • Quickstart: install helipod and see the whole loop end to end.

On this page