Skip to content

Install

Packages, project layout, configuration, and the runtime entry points.

Reviewed Updated 10 days ago · Flare 0.3

Two packages and an optional flare.json get you a Flare app ready to build() and run on Node or Cloudflare Workers.

Terminal window
pnpm add @flare-ts/core @flare-ts/lib

npm install, yarn add, and bun add work the same.

@flare-ts/core is the framework entry point: host, HTTP arc, DI, config. Import most symbols from there. Add @flare-ts/lib when you import schema helpers directly, for example schema() from @flare-ts/lib/schema. Entry points and subpaths for both packages are in the API Reference.

@flare-ts/lib has no runtime dependencies, and @flare-ts/core depends only on @flare-ts/lib, so Flare pulls no third-party packages into your node_modules.

A minimal Flare project is one entry module plus an optional flare.json:

my-app/
├── src/
│ └── main.ts # FlareHost, routes, host.build(), then run() or export()
├── flare.json # optional: port, logging, custom config sections
├── package.json # "type": "module"
└── tsconfig.json

Flare is ESM only, so the project must be too: package.json needs "type": "module". npm init does not add it, and without it Node parses src/main.ts as CommonJS and the first import fails with Cannot use import statement outside a module.

Run the entry file with Node directly:

Terminal window
node src/main.ts

Node runs TypeScript natively since 22.18, with one gap that matters here: decorators. An app that registers routes inline (host.http.get(...)) runs under plain node. Controller classes register routes with decorators (@Get, @Post), which Node can’t parse, so run a controller app with a TypeScript runner instead:

Terminal window
npx tsx src/main.ts

The runner is also the answer on Node versions below 22.18.

flare.json at the project root is optional, and every field has a built-in default. On Node it’s read once at host.build(). Cloudflare Workers have no filesystem, so there the same values are passed to the adapter in code (see Runtimes below).

{
"host": { "port": 4000 },
"log": { "level": "debug", "format": "pretty" }
}

Override any field per environment with a double-underscore variable: FLARE__host__port=4000, FLARE__log__level=warn.

Configuration explains the model: typed sections, flareConfig(...) for your own config, validation at build(). The flare.json reference documents every field, its default, the resolution order, and the override rules.

The same registrations run on both supported runtimes. What changes is the adapter you hand FlareHost and the entry-point call on the built app.

RuntimeAdapterEntry point
Node.js ≥ 22import { node } from "@flare-ts/core/node"app.run()
Cloudflare Workersimport { cf } from "@flare-ts/core/cloudflare"export default app.export()
Bun / Deno@flare-ts/core/bun, @flare-ts/core/denoStubs: host.build() throws today
import { FlareHost, FlareResponse } from "@flare-ts/core";
import { node } from "@flare-ts/core/node";
const host = new FlareHost(node);
host.http.get("/health", () => new FlareResponse(200, { ok: true }));
const app = host.build();
app.run();

build() reads flare.json; run() binds the port (3000 by default) and serves. Run and shutdown covers ports, readiness, and graceful drain.

// @ground:skip
import { FlareHost, FlareResponse } from "@flare-ts/core";
import { buildCf } from "@flare-ts/core/cloudflare";
import flareJson from "../flare.json" with { type: "json" };
const host = new FlareHost(buildCf(flareJson));
host.http.get("/health", () => new FlareResponse(200, { ok: true }));
const app = host.build();
export default app.export();

Workers never read a file at runtime, so flare.json is imported and bundled into the Worker instead: buildCf(flareJson) hands those values to the host at build(). Pass the Worker env as a second argument, buildCf(flareJson, env), when you also want FLARE__* overrides applied. The bare cf adapter carries no configuration at all, which is fine when the defaults are.

Enable nodejs_compat in wrangler.toml; Flare’s logger uses AsyncLocalStorage, and the Worker fails to start without the flag. One surface differs by design: host.singleton doesn’t exist on a Cloudflare host, so register per-request services with host.scoped(). Cloudflare Workers covers deploy, bindings, secrets, and Durable Objects.

The adapters exist so import paths stay stable, but host.build() throws on both today. See Bun and Deno.