Documentation
Cloudflare Workers
Kynetra FX ships a first-class Cloudflare Workers adapter via @kynetra/fx-cloudflare. One function call — cloudflare(app) — converts any FX application into a standard Workers export, giving you zero cold-starts, global distribution, and full access to Cloudflare bindings like D1, KV, R2, and Queues.
Overview#
The cloudflare() function (also exported as toCloudflareHandler()) wraps your FX application and returns the standard { fetch(request, env, ctx) } export shape that Cloudflare Workers expects. Your application logic stays identical — the adapter bridges Cloudflare's request/response types to the FX context model.
Because app.fetch(request) is portable by design, the same application can run on Bun, Deno, Node.js, or any other FX-supported runtime without changes to your route handlers. Swapping cloudflare(app) for the Node adapter is sufficient to self-host.
- Zero cold-starts — V8 isolates spin up in microseconds, not seconds.
- Globally distributed — your code runs at the edge closest to the user automatically.
- No long-running processes — the Workers execution model is stateless and event-driven.
- Bindings available via
ctx.env— typed through yourEnvinterface. - Full Web Crypto, Fetch, Streams, and URLPattern support included.
Installation#
Install the adapter package
@kynetra/fx-cloudflare alongside the core framework.npm install @kynetra/fx @kynetra/fx-cloudflareInstall Wrangler
npm install --save-dev wranglerCreate wrangler.toml
wrangler.toml at the root of your project. See the wrangler.toml setup section below for a full example.Start the local dev server
npx wrangler devMinimal Worker#
Export the result of cloudflare(app) as the default export of your Worker entry file. Wrangler reads this export automatically.
import { createFX } from '@kynetra/fx'import { cloudflare } from '@kynetra/fx-cloudflare' const app = createFX({ runtime: 'cloudflare' }) app.get('/', (ctx) => ctx.json({ hello: 'world' })) app.get('/env', (ctx) => ctx.json({ bucket: ctx.env.BUCKET_NAME })) return cloudflare(app)Tip
{ runtime: 'cloudflare' } to createFX so the framework can optimise for the Workers execution environment — for example, avoiding Node.js-specific APIs and enabling edge-specific features like geo headers.wrangler.toml setup#
The wrangler.toml file declares your Worker's entry point, compatibility date, environment variables, and binding declarations. Bindings defined here become properties on the env object passed to every request.
name = "my-api"main = "src/index.ts"compatibility_date = "2024-01-01" [vars]APP_ENV = "production" [[d1_databases]]binding = "DB"database_name = "my-db"database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" [[kv_namespaces]]binding = "KV"id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"The compatibility_date field pins the set of Cloudflare runtime APIs available to your Worker. Always advance this date when you want to adopt newer platform features, and test your Worker thoroughly after doing so.
Accessing bindings via ctx.env#
Every Cloudflare binding — D1 databases, KV namespaces, R2 buckets, secrets, and plain variables — is available on ctx.env inside your route handlers and middleware. The type of ctx.env is inferred from the generic parameter you pass to createFX, which should match your Env interface.
import { createFX } from '@kynetra/fx'import { cloudflare } from '@kynetra/fx-cloudflare' interface Env { DB: D1Database KV: KVNamespace BUCKET_NAME: string JWT_SECRET: string} const app = createFX<{ Env: Env }>({ runtime: 'cloudflare' }) // ctx.env is typed as Envapp.get('/secret', (ctx) => { const secret = ctx.env.JWT_SECRET return ctx.text(secret ? 'present' : 'missing')}) app.get('/kv/:key', async (ctx) => { const value = await ctx.env.KV.get(ctx.params.key) if (value === null) return ctx.json({ error: 'not found' }, { status: 404 }) return ctx.text(value)}) return cloudflare(app)Note
JWT_SECRET) are declared in the Cloudflare dashboard under Workers > Settings > Variables and are never stored in wrangler.toml. During local development, place them in a .dev.vars file at the project root — Wrangler reads this file automatically and keeps it out of version control.Env typing reference#
Cloudflare provides TypeScript types for every binding category. Import them from the @cloudflare/workers-types package (installed automatically by Wrangler).
| Name | Type | Description |
|---|---|---|
| D1Database | type | Bound D1 database — use with d1Sql() or d1Store() from @kynetra/fx-cloudflare. |
| KVNamespace | type | Bound KV namespace — provides get, put, delete, and list methods. |
| R2Bucket | type | Bound R2 bucket — provides object storage operations. |
| Queue<T> | type | Bound Queue producer — use send() to enqueue messages. |
| DurableObjectNamespace | type | Bound Durable Object namespace — provides idFromName() and get(). |
| string | type | Plain environment variable or secret declared under [vars] or in the dashboard. |
Edge model considerations#
Running on Cloudflare Workers means embracing the edge execution model. A few things work differently here compared to a traditional long-running server:
No persistent in-memory state
Each Worker invocation is isolated. Global variables are re-initialised on each cold start (though V8 isolates are often reused in practice). Do not rely on module-level variables to share state across requests. Use D1, KV, or Durable Objects instead.
CPU time limit
Workers on the free plan are limited to 10 ms of CPU time per request. Paid plans raise this to 30 seconds of wall-clock time. CPU-intensive operations such as WASM compilation or large in-memory sorts should be profiled carefully. See WASM languages for guidance on bundling native code.
No outbound TCP
Workers cannot open raw TCP connections. All outbound I/O must go through the Fetch API, Cloudflare Hyperdrive (for Postgres), or a binding. This means traditional database drivers that use raw sockets will not work — use D1 or a Hyperdrive-backed Postgres adapter instead.
Request context lifetime
The ctx.waitUntil(promise) API lets you extend a Worker's lifetime beyond the response so that background tasks (such as logging or cache warming) can complete asynchronously. Access the underlying execution context via ctx.executionCtx.
app.post('/events', async (ctx) => { const event = await ctx.jsonBody() // Respond immediately, flush analytics in the background ctx.executionCtx.waitUntil( fetch('https://analytics.internal/ingest', { method: 'POST', body: JSON.stringify(event), }) ) return ctx.json({ ok: true })})Compatibility date#
Cloudflare uses the compatibility_date field in wrangler.toml to gate breaking changes to the Workers runtime. Set it to the date you want to target, and advance it deliberately when you are ready to adopt newer behaviour.
- Use a recent date (within the past year) for new projects to get the latest APIs and bug fixes.
- Check the Cloudflare compatibility flags reference before advancing the date on an existing Worker.
- The
compatibility_flagsarray lets you opt in to individual flags ahead of their default date.
# Opt in to specific flags before they become the defaultcompatibility_date = "2024-01-01"compatibility_flags = ["nodejs_compat", "streams_enable_constructors"]Warning
compatibility_date can change runtime behaviour for existing Workers. Always test in a staging environment — deploy to a non-production environment with npx wrangler deploy --env staging — before rolling out to production.Deploying#
Use Wrangler to deploy your Worker to the Cloudflare global network. The command bundles your TypeScript, uploads the script, and outputs the deployment URL.
# Deploy to productionnpx wrangler deploy # Deploy to a named environment (defined in wrangler.toml under [env.staging])npx wrangler deploy --env stagingTo set up continuous deployment, point a GitHub Actions workflow (or any CI system) atwrangler deploy and supply your CLOUDFLARE_API_TOKEN as a secret. Wrangler picks up the token from the environment automatically.
name: Deployon: push: branches: [main]jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - run: npm ci - run: npx wrangler deploy env: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}Note