Hosts
Your Module, Controller, and provider classes are host-agnostic — the same source compiles and runs
unchanged whether the Vite dev server, a standalone Node process, Nitro, or Nuxt serves it. What changes
between hosts is only who owns the live fetch server and triggers the compile. You write your app
once; you pick a host, and separately, a deploy target for where that host actually runs.
Pick a host
Each host is a Heximon package you add to a config file. It compiles your modules and mounts the generated
routes onto its own server — you never write a bootstrap. Every host reads the same heximon.config.ts
config file for compiler plugins, so switching hosts never touches your Heximon config.
| Host | Package | Role |
|---|---|---|
| Dev + standalone build | @heximon/build/vite | The heximon() plugin — runs the compiler in memory and serves the app on pnpm dev, and on vp build emits a standalone dist/server.js for Node/Bun/Deno (no second config, no host-entry file). |
| Other bundlers | @heximon/build | /rollup, /rolldown, /esbuild, /webpack, /rspack are build tiers that compile your modules and emit a runnable { app, fetch } bundle — for folding Heximon into a project already built by one of those bundlers. |
| Standalone (Node / Bun / Deno) | @heximon/http/serve | The universal runner vp build's entry calls — boots the built { fetch } bundle as a long-lived server, picking node:http / Bun.serve / Deno.serve automatically. No Nitro. |
| Node / Nitro / edge | @heximon/nitro | heximonNitroModule compiles your modules and mounts the route table inside Nitro — Node, Cloudflare Workers, Deno, Bun, Lambda from one codebase, picked by Nitro's preset. |
| Nuxt | @heximon/nuxt | Serves a Heximon API alongside your Vue pages on Nuxt's own Nitro server, with SSR cookie propagation. |
| Library (publish a module) | @heximon/library | Compiles one Heximon module into a precompiled, installable package another app's imports array pulls in. |
The same modules drive every host. A controller injects its dependencies from its constructor and declares routes by parameter type regardless of which host lands it.
Durable Objects, WebSockets, and Server-Sent Events (@heximon/durable, @heximon/websocket, @heximon/sse)
are transports, not hosts — stateful capabilities that ride whichever host above is active. See
Durable Objects, WebSockets, and
Server-Sent Events.
Develop with the Vite plugin
In development the heximon() Vite plugin is the host. It runs the compiler over src/ on startup,
holds the generated wiring as in-memory virtual modules, serves the app, and recompiles on every save —
so pnpm dev is the only command you need while building. The plugin itself takes no arguments; your Heximon
config — compiler plugins and deploy platform — lives in heximon.config.ts next to it.
import heximon from "@heximon/build/vite";
import { defineConfig } from "vite-plus";
export default defineConfig({
plugins: [heximon()],
server: { port: 3000 },
});
import { defineHeximonConfig } from "@heximon/build";
import { HttpPlugin } from "@heximon/http/compiler";
export default defineHeximonConfig({ plugins: [new HttpPlugin()] });
You add one compiler plugin per Heximon feature you use — new CqrsPlugin(), new DomainEventsPlugin(),
and so on. The plugin set is the only thing that grows as your app does; the host wiring stays the same.
Use another bundler with unplugin
Most apps develop on the Vite plugin and deploy with Nitro (below) — never hand-writing a bundler config. But
if your project already builds with another bundler, @heximon/build carries the same
compile step to Rollup, Rolldown, esbuild, webpack, and rspack through a per-bundler entry.
The
@heximon/build/vite entry is the complete Vite plugin — the build-error page, the JSON preview,
recompile-on-change, auto-reload, and WebSocket-upgrade handling in dev — because it rides the shared
@heximon/dev core.
The /rollup, /rolldown, /esbuild, /webpack, and /rspack entries are build tiers:
they compile your src/ to .heximon/ and let the bundler emit a runnable server bundle (diagnostics to the
terminal — the live dev server is Vite-only). Every one of these factories takes no arguments — the same
heximon.config.ts drives them all.
import heximon from "@heximon/build/vite";
import { defineConfig } from "vite-plus";
export default defineConfig({
plugins: [heximon()],
});
Run standalone on Node, Bun, or Deno
When you want the built bundle to run as a plain long-lived server — no Nitro — vp build emits it from the
same vite.config.ts that serves vp dev, with no second bundler config and no host-entry file:
vp build # → dist/server.js
node dist/server.js # or: bun dist/server.js / deno run -A dist/server.js — same file, every runtime
Under the hood, @heximon/http/serve is the universal runner: vp build's synthesized entry hands the generated
fetch to srvx, which picks the platform server (node:http / Bun.serve /
Deno.serve) automatically. To control the listener (port/hostname) or wrap the app, write your own one-line
host entry and bundle it with a build tier above (or point Vite's build.rollupOptions.input at it):
import { serve } from "@heximon/http/serve";
import { fetch } from "./.heximon/server.js";
await serve(fetch); // port defaults to PORT, then 3000
Node, Bun, and Deno run the full framework path — HTTP, DI, the ambient Context (AsyncLocalStorage), and
CQRS/event dispatch — unchanged, and the generated entry drains in-flight requests on SIGTERM on each.
Capabilities that need a specific platform (native addons like better-sqlite3, the cloudflare:workers
Durable Object / D1 APIs) stay on Node or the Cloudflare runtime.
Deploy to Node, Workers, and the edge with Nitro
For production, Nitro is a host that covers almost every runtime from one codebase — there is
no separate "plain Node" story; Nitro is the Node host, and every other runtime is just a preset of the
same build: node-server for a long-running process, cloudflare_module for Workers, all from one codebase.
You list the bare module in nitro.config.ts — it accepts no inline Heximon config. Compiler plugins live in
the same heximon.config.ts the Vite plugin reads. Nitro's build runs Heximon's compiler over your
server directory and mounts the routes; there's no compile script to call. Nitro's own preset decides the
deploy platform — heximon.config.ts's plugins are the only thing you declare there for Nitro.
import { heximonNitroModule } from "@heximon/nitro/module";
import { defineNitroConfig } from "nitro/config";
export default defineNitroConfig({
compatibilityDate: "2025-07-04",
serverDir: "server",
modules: [heximonNitroModule],
});
import { defineHeximonConfig } from "@heximon/build";
import { HttpPlugin } from "@heximon/http/compiler";
export default defineHeximonConfig({ plugins: [new HttpPlugin()] });
Routes mount at their own contract paths — Controller<"/api"> owns /api. The host adds no prefix, so
per-route rules, caching, and headers from Nitro apply to each route individually.
Serve a Heximon API inside Nuxt
When you want a Vue frontend and a Heximon API on the same server, Nuxt 5 is the
host. Your Heximon modules live under Nuxt's server/ directory; your Vue pages live under srcDir.
The
module forwards to the Nitro host underneath and accepts no inline Heximon config — the integration is just
one string in modules, with compiler plugins declared in the same heximon.config.ts. Nuxt's own
Nitro preset decides the deploy platform.
export default defineNuxtConfig({
compatibilityDate: "2025-07-04",
srcDir: "app",
modules: ["@heximon/nuxt"],
});
import { defineHeximonConfig } from "@heximon/build";
import { HttpPlugin } from "@heximon/http/compiler";
export default defineHeximonConfig({ plugins: [new HttpPlugin()] });
Because the API and the frontend share one Nitro server, cookies set during SSR propagate to your Heximon routes without a network hop — login on a page, and a server-rendered fetch already carries the session.
Publish a module as a library
When a feature is meant to be reused across apps or teams, library mode compiles one
Heximon module into a precompiled npm package. The consuming app lists it in imports, exactly like a local
module, and links against a typed factory plus a manifest — never your source.
See
Library mode for the requires/providers/exports boundary and the peerDependencies
rule that keeps DI identity intact across the package boundary.
Split one deploy across two runtimes
On a hybrid deploy target (Vercel Functions, Netlify Functions) a single app can place some controllers on a fast edge function and others on a node function where the database lives — one build, two bundles, routed by prefix. See Mixed-runtime split.
Reach the edge with stateful transports
HTTP is the default transport, but stateful workloads need more. Controllers covers the HTTP dispatcher; Durable Objects adds Cloudflare's stateful actors, and WebSockets / Server-Sent Events add live connections.
These aren't separate hosts — they're plugins that ride whichever host is active, so the same Nitro
deployment serves your HTTP routes and your ws:// channels together.
Every host shares one property: the compiled output has no eval and no JIT. That's the point — it runs on
edge runtimes where reflection-based frameworks can't, because the wiring was decided at build time, not
discovered at boot.
See also
- Compile on other bundlers — the unplugin factory for Rollup, Rolldown, and esbuild.
- Deploy on Nitro — a host across Node, Cloudflare Workers, Deno, Bun, and Lambda.
- Deploy overview —
vp buildand the deploy strategies per vendor. - Standalone runtime
— one built bundle run as a server on Node, Bun, and Deno via
@heximon/http/serve. - Integrate with Nuxt — a Heximon API and Vue pages on one Nitro server, with SSR cookies.
- Publish a module — ship a precompiled Heximon module as an installable package.
- Controllers — the HTTP dispatcher every transport rides.
- The nitro-presets gap example — the same controller served under Nitro, proving the app source is host-agnostic.
- Nuxt module — a
Heximon API under
server/alongside Nuxt pages, with a cookie set during SSR. - Durable Objects — a stateful Cloudflare actor under compile-time DI.
Mixed-runtime split
Place one app's controllers across edge and node runtimes in a single deploy — the compiler partitions the DI graph by runtime, proves each edge tier is edge-safe before deploy, and emits separate functions routed by prefix. Vercel Functions and Netlify Functions.
Cross-service events
Fan an integration event out to independently deployed services — the per-service-queue split, Redis Streams and SNS-to-SQS native broadcast, the Cloudflare DO-router escape hatch, and QStash push broadcast for freezing platforms — selected via crossService.transport.