Docs

Edge & bindings

On Cloudflare and other edge runtimes, your platform bindings (KV, D1, Durable Objects, secrets) reach handlers through a fully-typed c.env.

Typed c.env

server<Env>() threads your Env type through the whole app: c.env is Env in every handler and in derive/decorate middleware - read c.env.KV directly, no cast. app.fetch(req, { env }) / toFetchHandler type-check the bindings too. In @nifrajs/web, a route's loader/action gets the same typed env.

TS
// server<Env>() types the platform env end-to-end - c.env is your Env in every
// handler AND in derive/decorate middleware, with no cast.
interface Env { KV: KVNamespace; DB: D1Database }

const app = server<Env>()
  .get("/u/:id", async (c) => {
    const cached = await c.env.KV.get(c.params.id)   // c.env is typed Env
    return cached ?? "miss"
  })

export default { fetch: (req: Request, env: Env, ctx: ExecutionContext) =>
  app.fetch(req, { env, waitUntil: ctx.waitUntil.bind(ctx) }) }

Export through toFetchHandler rather than handing the app out directly. Besides env and ctx.waitUntil, it tells nifra the request came from the platform's own HTTP parser, which lets the bounded-body cap trust the declared Content-Length as a real transport frame instead of re-counting every JSON body. A bare export default app has no such signal and pays the extra pass unless you set server({ trustBodyFraming: true }).

Background work

c.waitUntil(promise) keeps work alive after the response is sent - cache writes, analytics, fan-out - without blocking the user. (Typing is not validation: platform bindings are trusted inputs; validate anything untrusted at the boundary.)

TS
// c.waitUntil keeps background work alive past the response (analytics, cache writes).
app.post("/event", (c) => {
  c.waitUntil(c.env.KV.put("last", Date.now().toString()))
  return { ok: true }   // returns immediately; the put finishes in the background
})

Validation on the edge

Edge runtimes block dynamic code generation (new Function), which trips many schema libraries. Nifra's t handles it transparently: it compiles a fast validator on Bun and Node, and falls back to an eval-free checker on Cloudflare Workers, Vercel Edge, and Deno Deploy - the same routes validate everywhere, with no edge-specific schema module. Because core validates any Standard Schema, you can also bring Zod, Valibot, or ArkType (all eval-free) - only t also emits OpenAPI from the same definition.

Size is the other edge axis. t is TypeBox-backed - it carries the whole JSON Schema type system - so a validated app on t is heavier than one on a lean validator. Validation is per route, so reach for t where you want the contract (OpenAPI/MCP for free) and Valibot where the Workers cold-start bundle is what matters: a Valibot-validated Nifra app measures ~16 KB gzipped. Both are first-class, and you can mix them in one app.

Scheduled (cron)

Pass scheduled to toFetchHandler to also export a Workers cron handler for a [triggers] schedule. It gets the platform controller plus the same typed env and waitUntil as your request handlers.

TS
// doc-check: skip - `env.KV` is typed by your app's `Env` (the ./app import), supplied by the reader.
import { toFetchHandler } from "@nifrajs/core/server"
import { app } from "./app"

// Export fetch + a cron handler. Wire the schedule in wrangler.toml: [triggers] crons = ["0 * * * *"]
export default toFetchHandler(app, {
  scheduled: (controller, { env, waitUntil }) =>
    waitUntil(env.KV.put("last-run", String(controller.scheduledTime))),
})