Kynetra FX

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/fx and 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

Always call .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.

1

Install

bun add @kynetra/fx — no adapter needed.
2

Create the entry file

Export a default object with port and fetch. Pass { runtime: 'bun' } to createFX for Bun-specific defaults.
3

Run

bun run server.ts — Bun compiles TypeScript on the fly, no build step required.
server.ts
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.

server.ts
import { createFX, cors, logger, requestId, secureHeaders } from '@kynetra/fx'
 
const app = createFX({ runtime: 'bun' })
 
// Middleware — applied in registration order
app.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:

server-ws.ts
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.

1

No install step

Deno resolves npm:@kynetra/fx at run time. There is no deno install step.
2

Create the entry file

Import from npm:@kynetra/fx and pass { runtime: 'deno' } to createFX.
3

Run with permissions

Deno requires explicit permission flags. At minimum: --allow-net --allow-env.
server.ts
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:

server.ts
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:

terminal
# Minimal: only network and environment variable access
deno run --allow-net --allow-env server.ts
 
# With read access for static files
deno run --allow-net --allow-env --allow-read=./public server.ts

Warning

Avoid using --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 readspackage.json out of the box. Deno uses the npm: 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; use esbuild or rollup for 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 + entry files
// app.ts — runtime-agnostic, importable everywhere
import { 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.ts
import { buildApp } from './app'
const app = buildApp()
return { port: 3000, fetch: app.fetch.bind(app) }
 
// deno-entry.ts
import { buildApp } from 'npm:./app'
const app = buildApp()
Deno.serve({ port: 3000 }, app.fetch.bind(app))
 
// node-entry.ts
import { 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

The 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#