Documentation
Bun & Deno
Bun and Deno both implement the Web Fetch API natively, so app.fetch plugs straight into Bun.serve and Deno.serve — no adapter package required. The same application code runs unmodified across runtimes.
Overview#
Kynetra FX is designed around the standard (Request) => Promise<Response> interface. Bun exposes this as the fetch property of the default export; Deno accepts it as the second argument to Deno.serve. In both cases you wire app.fetch in directly — no shim, no wrapper library.
- No extra dependencies — just
@kynetra/fxand the runtime. - Full middleware stack works identically:
logger,cors,secureHeaders,requestId, and all others. - TypeScript support is first-class in both runtimes without a separate build step.
- Move between Bun, Deno, and Node by swapping only the entry point file.
Tip
.bind(app) when passing app.fetch as a bare reference. Because fetch reads from this internally, passing it unbound causes a runtime error. Alternatively write (req) => app.fetch(req).Bun quick start#
Bun reads the default export from your entry file. When the export has a fetch property, Bun treats it as an HTTP server definition. Set port alongside fetch and Bun starts listening immediately — no separate server creation call is needed.
Install
bun add @kynetra/fx — no adapter needed.Create the entry file
port and fetch. Pass { runtime: 'bun' } to createFX for Bun-specific defaults.Run
bun run server.ts — Bun compiles TypeScript on the fly, no build step required.import { createFX } from '@kynetra/fx' const app = createFX({ runtime: 'bun' }) app.get('/', (ctx) => ctx.json({ runtime: 'bun', hello: 'world' }))app.get('/health', (ctx) => ctx.json({ status: 'ok' })) return { port: 3000, fetch: app.fetch.bind(app),}Bun with middleware#
Register middleware with app.use() before defining routes. Middleware runs in registration order for every request. The example below adds request IDs, structured logging, security headers, and CORS — all from the core package, no extra installs.
import { createFX, cors, logger, requestId, secureHeaders } from '@kynetra/fx' const app = createFX({ runtime: 'bun' }) // Middleware — applied in registration orderapp.use(requestId())app.use(logger())app.use(secureHeaders())app.use(cors({ origin: '*' })) app.get('/api/status', (ctx) => ctx.json({ ok: true })) app.get('/api/echo', async (ctx) => { const body = await ctx.req.json() return ctx.json({ echo: body })}) return { port: Bun.env.PORT ? parseInt(Bun.env.PORT) : 3000, fetch: app.fetch.bind(app),}Read environment variables from Bun.env (a type-safe alias for process.env) rather than accessing process.env directly, to keep the code clear about runtime intent.
WebSockets on Bun
Bun's server object accepts a websocket handler alongside fetch. Your HTTP routes and WebSocket handlers coexist in the same export without any conflict:
import { createFX } from '@kynetra/fx' const app = createFX({ runtime: 'bun' }) app.get('/', (ctx) => ctx.text('Hello from Bun')) return { port: 3000, fetch: app.fetch.bind(app), // Bun WebSocket handler — lives alongside app.fetch websocket: { message(ws, message) { ws.send(`echo: ${message}`) }, },}Deno quick start#
On Deno, import @kynetra/fx from the npm: specifier — no import map or package.json required. Pass app.fetch as the second argument to Deno.serve.
No install step
npm:@kynetra/fx at run time. There is no deno install step.Create the entry file
npm:@kynetra/fx and pass { runtime: 'deno' } to createFX.Run with permissions
--allow-net --allow-env.import { createFX } from 'npm:@kynetra/fx' const app = createFX({ runtime: 'deno' }) app.get('/', (ctx) => ctx.json({ runtime: 'deno', hello: 'world' }))app.get('/health', (ctx) => ctx.json({ status: 'ok' })) Deno.serve({ port: 3000 }, app.fetch.bind(app))Deno with env and middleware#
Read configuration from Deno.env.get(). All Kynetra FX middleware imports work identically — prefix them with npm: when using the npm specifier:
import { createFX } from 'npm:@kynetra/fx'import { logger, secureHeaders, cors } from 'npm:@kynetra/fx' const app = createFX({ runtime: 'deno' }) app.use(logger())app.use(secureHeaders())app.use(cors({ origin: Deno.env.get('ALLOWED_ORIGIN') ?? '*' })) app.get('/hello', (ctx) => ctx.text('Hello from Deno')) app.get('/env', (ctx) => ctx.json({ node_env: Deno.env.get('NODE_ENV') ?? 'development' })) const port = parseInt(Deno.env.get('PORT') ?? '3000')Deno.serve({ port }, app.fetch.bind(app))Deno permissions
Deno's permission model requires you to explicitly grant each capability at the command line. For most Kynetra FX applications you need at least --allow-net and --allow-env:
# Minimal: only network and environment variable accessdeno run --allow-net --allow-env server.ts # With read access for static filesdeno run --allow-net --allow-env --allow-read=./public server.tsWarning
--allow-all in production. Grant only the permissions your application actually uses — this is one of Deno's key security advantages.Bun vs. Deno — tradeoffs#
Both runtimes support app.fetch natively, but they differ in focus and ecosystem. Choose based on your deployment context:
- Startup speed — Bun typically starts faster due to its JavaScriptCore engine and native bundler integration. Deno V8 cold starts are slightly slower but warm quickly.
- npm compatibility — Bun has near-complete npm compatibility and reads
package.jsonout of the box. Deno uses thenpm:specifier and may occasionally hit edge-case compatibility issues with native addons. - Security model — Deno's explicit permission flags provide a strong default-deny sandbox. Bun trusts the environment by default, similar to Node.
- TypeScript — both run TypeScript natively without a build step; Deno additionally supports JSX and has a built-in formatter and linter (
deno fmt,deno lint). - Bundler — Bun ships a high-performance bundler (
bun build). Deno's bundler was removed in v2; useesbuildorrollupfor Deno production bundles. - WebSockets — Bun provides a native WebSocket API inside the server export. Deno uses
Deno.upgradeWebSocket()inside a route handler. - Deployment targets — Bun works well on Linux VMs, Docker, and Railway. Deno Deploy (Deno's edge platform) runs Deno natively and is a compelling alternative to Cloudflare Workers for teams already in the Deno ecosystem.
Sharing code across runtimes#
The cleanest multi-runtime pattern is to extract your routes and middleware into a shared app.ts module that exports a factory function, then create thin runtime-specific entry files. The factory receives no runtime-specific types, so it can be imported anywhere:
// app.ts — runtime-agnostic, importable everywhereimport { createFX, logger, cors } from '@kynetra/fx' export function buildApp() { const app = createFX() app.use(logger()) app.use(cors()) app.get('/api/ping', (ctx) => ctx.json({ pong: true })) return app} // bun-entry.tsimport { buildApp } from './app'const app = buildApp()return { port: 3000, fetch: app.fetch.bind(app) } // deno-entry.tsimport { buildApp } from 'npm:./app'const app = buildApp()Deno.serve({ port: 3000 }, app.fetch.bind(app)) // node-entry.tsimport { serve } from '@kynetra/fx-node'import { buildApp } from './app'const app = buildApp()serve(app, { port: 3000 })This pattern also simplifies testing: import buildApp() in your test suite, call app.fetch(new Request(...)) directly, and assert on the Response — no running server needed.
Note
runtime hint passed to createFX is optional. When omitted, the framework auto-detects the environment. Passing it explicitly is recommended in production to avoid any ambiguity during startup.See also#
- Node.js —
@kynetra/fx-nodeadapter fornode:http - Cloudflare Workers — deploy to the edge with Wrangler
- Self-hosting — Docker, systemd, and reverse proxies