Documentation
Playground
The Kynetra FX playground runs the real framework — compiled to a ~32 KB browser bundle — entirely in your browser. Write route handlers, middleware, and contracts, click Run, and see the HTTP response immediately. No install, no server, no build step required.
Overview#
The playground is not a simulator or a mock. It executes the actual @kynetra/fx core — the same code that ships to production — in a sandboxed in-browser environment powered by @kynetra/fx-browser. Requests are simulated HTTP calls routed through the real FX context and middleware pipeline, so the behaviour you see in the playground matches what runs on a Cloudflare Worker or Node server exactly.
- Real framework code — not a DSL or simplified API.
- TypeScript support with type-checked input in the editor.
- Preset examples covering every major framework feature.
- Shareable URLs — playground state is encoded in the URL hash.
- Simulated HTTP requests sent to the in-browser app via a request panel.
Tip
@kynetra/fx.How it works#
The @kynetra/fx-browser package is a pre-bundled, tree-shaken build of the core framework targeting the browser environment. It weighs approximately 32 KB gzipped and exposes the same createFX, fx, middleware helpers, and plugin system that you would import from @kynetra/fx in a server environment.
When you click Run, the playground:
- Transpiles your TypeScript in-browser using a lightweight compiler worker.
- Evaluates the module and reads the
export defaultas the FX application. - Constructs a
Requestobject from the request panel inputs (method, path, headers, body). - Calls
app.fetch(request)and renders the returnedResponsein the output panel.
Because the framework uses the standard Request / Response Web API throughout, every middleware, contract, plugin, and hook you write behaves identically whether it is executed inside the browser bundle or on a server runtime.
Note
Bun, Deno, D1Database, and process — are not available. See Cloudflare Workers and Node.js for runtime-specific features.Getting started#
Open the playground
Pick a preset (or start from scratch)
Edit the code
Send a request
/users/42), optionally add headers or a JSON body. Then click Run or press Cmd+Enter (Ctrl+Enter on Windows and Linux).Read the output
Preset examples#
The playground ships seven presets that demonstrate common patterns. Each preset is a self-contained, runnable example.
Hello World
The minimal createFX app — a single route returning JSON. Good as a starting point when you want to experiment with something specific.
import { createFX } from '@kynetra/fx' const app = createFX() app.get('/', (ctx) => ctx.json({ hello: 'world' })) return appMiddleware stack
Demonstrates requestId, logger, cors, and secureHeaders stacked in the correct order. The requestId middleware injects a unique ID header on every request; the others add standard security and CORS headers to every response.
import { createFX, cors, logger } from '@kynetra/fx'import { requestId, secureHeaders } from '@kynetra/fx-middleware' const app = createFX() app.use(requestId())app.use(logger())app.use(cors({ origin: '*' }))app.use(secureHeaders()) app.get('/', (ctx) => ctx.json({ hello: 'world' })) app.get('/users/:id', (ctx) => { return ctx.json({ id: ctx.params.id, name: 'Alice' })}) app.post('/echo', async (ctx) => { const body = await ctx.jsonBody() return ctx.json({ echo: body })}) return appContracts and validation
Shows app.route with an fx.object input schema. The framework validates the incoming request body against the schema before calling the handler; invalid bodies produce a 422 response automatically. See Contracts for the full API.
import { createFX, fx } from '@kynetra/fx' const app = createFX() app.route({ method: 'POST', path: '/greet', input: fx.object({ name: fx.string().min(1), age: fx.optional(fx.number().min(0)), }), handler: (ctx) => { const { name, age } = ctx.input return ctx.json({ message: `Hello, ${name}!`, age }) },}) app.route({ method: 'GET', path: '/items', query: fx.object({ page: fx.optional(fx.string()), limit: fx.optional(fx.string()), }), handler: (ctx) => { const { page = '1', limit = '20' } = ctx.query return ctx.json({ page: Number(page), limit: Number(limit), items: [] }) },}) return appAuth
Demonstrates jwtAuth middleware and the requireAuth guard. The playground generates a mock JWT so you can test the auth flow end-to-end without a real signing key. See Auth for full configuration options.
WASM guest
Uses a fake WASM guest module to demonstrate the wasmHandler integration. The guest receives a WasmRequest ABI and returns a WasmResponse. In production you would load an actual .wasm binary; the playground uses a JavaScript stand-in that mirrors the same interface. See WASM for the full polyglot guide.
SaaS kernel
Spins up a createSaasKernel instance with an in-memory store and wires it into a small API. You can create organisations, add members, and query feature flags — all using the real SaaS kernel code. See SaaS kernel for production setup.
OpenAPI
Calls generateOpenAPI on a small routed app and returns the spec as JSON. Send a GET /openapi.json request to see the generated document. See OpenAPI for full generation options.
Editor tips#
Keyboard shortcuts
Cmd+Enter/Ctrl+Enter— run the current code.Cmd+S/Ctrl+S— also triggers a run (mirrors the "save and run" habit).- Standard editor shortcuts (undo, redo, find, multi-cursor) are all available.
Share links
The playground encodes the entire editor contents and the current request configuration into the URL hash whenever you run. Copy the URL from your browser's address bar to share a runnable example with colleagues or in a bug report. Visiting the URL restores the editor and request panel exactly as you left them.
Tip
TypeScript
The editor understands TypeScript. Type annotations, generics, as casts, and non-null assertions are all valid. The type-checker runs in a worker so it does not block the editor; squiggles appear within a second of you stopping typing.
Types for @kynetra/fx are bundled with the browser build, so autocomplete and hover documentation work out of the box for all core exports including FXContext, FXMiddleware, fx.* schema types, and all middleware options.
Capabilities and limitations#
What you can do
- Define routes with any method — GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD.
- Stack middleware using
app.use. - Validate inputs and query strings with
fxschemas or Standard Schema adapters. - Use plugins, hooks, error handlers, and response helpers.
- Simulate multiple requests against the same app instance in sequence.
- Use Web Crypto, URLPattern, Streams, and other browser-available APIs.
- Import from multiple modules within the same editor session using dynamic
import().
Limitations
- No outbound network requests —
fetch()calls to external URLs are blocked by the browser sandbox. - No file system access — Node.js
fs,path, and similar modules are unavailable. - No Cloudflare-specific globals —
D1Database,KVNamespace, and other binding types exist as types only and cannot be instantiated. - No persistent state between page reloads — in-memory stores reset when you close the tab.
- Bundle size is fixed — you cannot install additional npm packages inside the playground.
Full runnable example#
The example below covers routing, middleware, path parameters, request body parsing, and error handling in a single file. Copy it into the playground to explore interactively.
import { createFX, cors, logger, fx } from '@kynetra/fx'import { requestId } from '@kynetra/fx-middleware' // In-memory store (resets each time you click Run)const users = new Map<string, { id: string; name: string; email: string }>([ ['1', { id: '1', name: 'Alice', email: 'alice@example.com' }], ['2', { id: '2', name: 'Bob', email: 'bob@example.com' }],]) const app = createFX() // Global middlewareapp.use(requestId())app.use(logger())app.use(cors({ origin: '*' })) // List usersapp.get('/users', (ctx) => { return ctx.json({ users: Array.from(users.values()) })}) // Get user by IDapp.get('/users/:id', (ctx) => { const user = users.get(ctx.params.id) if (!user) return ctx.json({ error: 'not found' }, { status: 404 }) return ctx.json(user)}) // Create user (validated)app.route({ method: 'POST', path: '/users', input: fx.object({ name: fx.string().min(1).max(100), email: fx.string().min(5), }), handler: (ctx) => { const id = String(users.size + 1) const user = { id, ...ctx.input } users.set(id, user) return ctx.json(user, { status: 201 }) },}) // Delete userapp.delete('/users/:id', (ctx) => { if (!users.has(ctx.params.id)) { return ctx.json({ error: 'not found' }, { status: 404 }) } users.delete(ctx.params.id) return ctx.json({ deleted: true })}) // Health checkapp.get('/health', (ctx) => ctx.json({ status: 'ok', ts: new Date().toISOString() })) return appNext steps#
Once you have a working prototype in the playground, moving it to production requires only minimal changes:
- Cloudflare Workers — wrap the app with
cloudflare(app)from @kynetra/fx-cloudflare and deploy with Wrangler. - Node.js — call
serve(app)from @kynetra/fx-node to start an HTTP server. - Contracts — explore the full schema API in the Contracts documentation.
- WASM — learn how to bundle real compiled guests in the WASM guide.