Skip to main content
Run forst generate, import functions from the generated client/, and call them over HTTP from any Node process — Express, Remix loaders, scripts. Point FORST_BASE_URL at a running server. forst generate emits TypeScript stubs, but the wire protocol is language-agnostic JSON. Examples use .ts because that is what the generator outputs. A minimal client call:

Quick start

Follow these steps from a public Forst function to a Node call:
1

Write a public Forst function

Only exported functions (capitalized names) are callable from Node.
2

Generate the client

This writes generated/types.d.ts, per-file *.client.ts modules, and client/index.ts with flat exports like export const Echo.
3

Install the runtime

Import from your generated client/ folder — not from @forst/client directly (that package is the HTTP transport).
4

Run a server

Development — hot reload while you edit .ft files:
Production — built-in HTTP server in your compiled binary (see Built-in HTTP server below):
5

Set the URL and call

Repository example: examples/in/rfc/embedded-invoke. Run task example:embedded-invoke:run from the monorepo root.

Multi-package projects

Forst packages are independent compilation units under one go.mod. A typical host layout:
File stems should match package names (bcrypt.ftpackage bcrypt). forst generate errors on mismatch unless you pass --allow-stem-package-mismatch. Repository example: examples/in/rfc/node-interop/multi-package-dev. Run task example:multipackage-dev from the monorepo root.
Generated clients use the Forst package name in invoke RPC metadata (package: "bcrypt", function: "Hash").

How the client picks a server

createInvokeClient (used inside generated client/index.ts) picks transport automatically: FORST_SKIP_SPAWN=1 forces HTTP connect and never auto-spawns forst dev. The forst dev HTTP contract (/invoke, /version, …) is documented in Dev server.

Runtime dev profile (embedded / host mode)

When server.embedded or node.hostMode is true in ftconfig.json, forst dev compiles and runs the project entry (same pipeline as forst run) instead of the per-invoke executor on :6320. Embedded invoke listens on server.port (default 6321). The CLI -port flag overrides only when you pass it explicitly; omitting -port uses ftconfig.json. Entry resolution order:
  1. CLI -entry
  2. dev.entry in ftconfig.json
  3. forst/main.ft under -root
  4. main.ft under -root
Hot reload: with default ftconfig.json, dev.hotReload is true and forst dev watches .ft files under -root, recompiles on change, and restarts the embedded binary. Set "watch": true explicitly or rely on hotReload. Compile failures are logged to stderr; the watcher stays alive so you can fix errors and save again. To disable watching and run once (same as forst run):
Node runtime binaries import forst/nodert. Declare that dependency in .forst-gomod/go.mod at your project root (monorepo dev example):
If any .ft file under -root uses Go FFI (import "golang.org/x/crypto/bcrypt", etc.), declare those modules in .forst-gomod/go.mod and run go mod download or go mod tidy there. Required for embedded invoke discovery across packages in Node-only projects. forst dev / forst run copy this into the .forst/run sandbox go.mod automatically. With @forst/cli, the shim also wires the cached runtime module internally — you do not set FORST_GOMOD_ROOT in app scripts. FORST_GOMOD_ROOT is for Forst compiler development only (working on the compiler itself). Set dev.profile to "executor" to force the legacy sidecar HTTP server even when server.embedded is enabled.

@forst/sidecar

@forst/sidecar is the lower-level Node client for spawn and connect modes. Generated client/ stubs use @forst/client, which may spawn forst dev via the sidecar when no URL is set. Install in your Node project:

Spawn mode (local development)

The sidecar starts forst dev as a child process. Expose a handler in Forst:
Call it from Node:

Connect mode (CI / monorepo)

Point at an already-running forst dev or built-in server:

Watch and generate

Configure watchRoots and optional watchGenerate so the sidecar debounces forst generate after reloads. That keeps generated/types.d.ts aligned while you edit .ft files. Call generateTypes() with configPath to pass -config through to the compiler.

Error handling

The sidecar client throws typed errors:
  • DevServerInvokeRejected: success: false in the invoke response
  • DevServerHttpFailure: HTTP errors, with optional parsed server error bodies
After start(), a version check compares /version contractVersion and compiler semver when parseable.

Built-in HTTP server

Your compiled Go binary can listen on localhost (default :6321) and run Forst functions directly when Node sends POST /invoke — no separate forst dev process and no go run per request. Enable with server.embedded in ftconfig.json:
Then forst build emits:
  • main.go — your transpiled code
  • *_forst_invoke_server.gen.go — dispatch registry + HTTP server
The companion init() starts the server. ForstInvokeWaitForShutdown() is appended to main so the process stays alive in long-running deployments. Same JSON contract as forst dev. Smoke test:

Generated output

After forst generate, expect this layout:
Flat exports let you write import { Echo } from "./client". The optional ForstClient class is there when you want explicit config in the constructor.

Configuration

ftconfig.jsonserver

Environment variables

When the built-in server starts, Forst writes .forst/invoke.ready with the server URL for tooling auto-discovery.

Host mode (Remix and similar)

Incremental migration often runs three channels in one deployment: Enable both server.embedded and node.hostMode in ftconfig.json. Remix loaders call generated client functions over :6321; Forst main calls legacy JS over the nodert socket. Full combined demo: examples/in/rfc/node-interop/remix-serve — a todo app with Remix loaders, embedded invoke, and nodert host mode (task example:node-interop-remix-serve:e2e). Nodert setup and readiness are covered in Call JavaScript from Forst.

Export rules

Only public functions with no unsatisfied Providers appear in the generated client. Functions that need wired dependencies belong in Forst main startup — not in /invoke from Node. The compiler rejects exports that would fail at runtime.

Troubleshooting

The compiled binary is not running, or main exited before the invoke server could accept traffic. Ensure server.embedded is true and the process blocks on ForstInvokeWaitForShutdown() (auto-appended by the compiler).
Check the POST /invoke body: package must match the Forst package name (usually main), and function must match the exported name exactly (Echo, not echo). List available functions with GET /functions.
Re-run npx forst generate after changing .ft files. In dev, configure watchGenerate on @forst/sidecar to debounce regeneration after reloads.
Wire providers in Go/main before the server starts. Provider-dependent functions are intentionally excluded from the generated client surface.
Confirm FORST_BASE_URL points at the built-in server (http://127.0.0.1:6321), not forst dev on 6320. Rebuild the Go binary after changing .ft sources.

Caveats

Built-in HTTP server, @forst/client, and @forst/sidecar are experimental. Pin package and compiler versions in production.

Check contractVersion

After upgrades, verify GET /version contractVersion matches what your client expects. @forst/sidecar checks this on start().

Spawn mode is local dev

Auto-spawn forst dev (via @forst/sidecar or @forst/client with no URL) is for local development only. Production should use the built-in HTTP server or explicit connect mode with FORST_BASE_URL.

Exported functions only

Only capitalized function names appear in discovery and the invoke registry.

Providers before expose

Wire with blocks in Go main before the server starts. Functions with unsatisfied Providers are excluded from the generated client. See Providers § Caveats.

Dev vs production URL

Executor profile (forst dev without embedded/host mode) listens on 6320 by default. Runtime profile (embedded or host mode) uses server.port from ftconfig.json (default 6321). A wrong FORST_BASE_URL looks like “works in dev, fails in prod”.

Examples

Generate client types

Emit .d.ts and client stubs from Forst sources.

Dev server

forst dev HTTP contract and endpoints.

Call JavaScript from Forst

Call legacy .ts/.js from Forst (nodert, host mode).

CLI reference

build, run, generate, and -root.

Client integration example

Prisma-like migration patterns in the repository.