> ## Documentation Index
> Fetch the complete documentation index at: https://better-router.bansal.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Worker entrypoint

> Register fetch, cron, queue, and inbound email handlers in src/worker.ts.

HTTP is the default. On Cloudflare, `src/worker.ts` is where you add the other Worker exports: **cron**, **queue consumers**, and **inbound email**. You can also wrap `fetch`. Node apps do not need this file.

On Cloudflare these map to the Worker `scheduled`, `queue`, and `email` exports. See [Cloudflare Workers](/cloudflare) and [Bindings](/bindings).

## `src/worker.ts`

```ts title="src/worker.ts" theme={null}
import { defineWorker } from "better-router/worker";

export default defineWorker({
  async scheduled(event, env, ctx) {
    console.log("cron", event.cron);
  },
  async queue(batch, env, ctx) {
    for (const message of batch.messages) {
      console.log(message.body);
      message.ack();
    }
  },
  async email(message, env, ctx) {
    console.log(message.from, message.to);
  },
});
```

The generated worker exports:

```ts theme={null}
export default {
  fetch,       // Hono app (unless you override fetch)
  scheduled,   // cron
  queue,       // queue consumer
  email,       // inbound email
}
```

If this file is missing, `fetch` still serves the Hono app. The other handlers exist and do nothing.

On Node and Bun, `vite` only serves `fetch`. The other handlers still exist on `dist/server` so a host can call them.

## Override `fetch`

Leave `fetch` unset to use the generated Hono app. Set it when you need to intercept the request first:

```ts title="src/worker.ts" theme={null}
import { defineWorker } from "better-router/worker";

export default defineWorker({
  async fetch(request, env, ctx) {
    if (new URL(request.url).pathname === "/health") {
      return new Response("ok");
    }
    // fall through is not automatic — import the app if you still want Hono
    return new Response("Not found", { status: 404 });
  },
});
```

Most apps should omit `fetch` and keep pages and APIs on Hono.

## Bindings in handlers

Non-HTTP handlers do **not** run inside the request `AsyncLocalStorage` context. `getEnv()` throws there. Use the `env` argument:

```ts title="src/worker.ts" theme={null}
import { defineWorker } from "better-router/worker";

export default defineWorker({
  async scheduled(event, env) {
    const db = env.DB as D1Database;
    await db.prepare("insert into heartbeats (at) values (?)").bind(event.scheduledTime).run();
  },
  async queue(batch, env) {
    const emails = env.EMAILS as Queue;
    for (const message of batch.messages) {
      await emails.send(message.body);
      message.ack();
    }
  },
});
```

Inside loaders, actions, and middleware, `getEnv()` is the same object as this `env`.

## Wrangler must list triggers

Cloudflare Cron Triggers **must** list the same expressions in `wrangler.jsonc`. Implementing `scheduled` in source is not enough for Cloudflare to fire the alarm.

```jsonc theme={null}
{
  "triggers": {
    "crons": ["*/5 * * * *"]
  },
  "queues": {
    "consumers": [{ "queue": "emails" }]
  }
}
```

A full app with D1, a queue producer/consumer, and cron is in [`examples/cloudflare`](https://github.com/bansal/better-router/tree/main/examples/cloudflare).

## Local

`vite dev` does not run `scheduled`, `queue`, or `email`. Use `wrangler dev` after `vite build` to exercise those handlers with Miniflare.
