# Request APIs

> Request context, redirects, environment helpers — what's safe in templates and what belongs server-side

Package: Koze
Canonical: https://kuratchi.dev/docs/koze/request-apis
Markdown: https://kuratchi.dev/docs/koze/request-apis.md

This page covers the runtime APIs your route templates and `$server/*` modules use to read request data, set cookies, redirect, and access environment values. For lifecycle hooks (`request` / `route` / `response` / `error`), see [Middleware](/docs/koze/middleware).

## Virtual modules in route `<script>` blocks

Route `<script>` blocks compile to **both** server-side render code and client-side hydration code. Anything imported into a top `<script>` has to make sense on both sides — that's why the framework exposes a deliberately narrow virtual-module surface for routes.

### `koze:request`

Safe request-derived values you can use in a route's top `<script>`:

```html
<script>
  import { url, pathname, searchParams, params, slug, method } from 'koze:request';

  const currentPath = pathname;
  const tab = searchParams.get('tab');
  const postId = params.id;
  const postSlug = slug;
</script>
```

| Export | Type | Description |
|--------|------|-------------|
| `url` | `URL` | Full request URL |
| `pathname` | `string` | URL pathname |
| `searchParams` | `URLSearchParams` | Query parameters |
| `params` | `Record<string, string>` | Route params (e.g. `{ id: '123' }`) |
| `slug` | `string \| undefined` | First param value or `params.slug` |
| `method` | `string` | HTTP method |

The compiler enforces this allow-list in route scripts. That's deliberate: anything *not* in this list (`request`, `headers`, `locals`, anything derived from auth state) cannot be safely serialized to the client.

### `koze:navigation`

Route-script redirect:

```html
<script>
  import { redirect } from 'koze:navigation';

  if (!user) {
    redirect('/auth/signin');
  }
</script>
```

`redirect()` throws a `RedirectError` that the framework catches and converts to a 303 response. Only meaningful server-side; calling it during client hydration is a no-op the framework strips at compile time. The same `redirect()` import also works inside `src/server/*.ts`.

Client navigation:

```html
<script>
  import { navigateTo } from 'koze:navigation';

  function openSettings() {
    navigateTo('/settings');
  }
</script>

<button onclick={openSettings()}>Settings</button>
```

`navigateTo(path, { replace })` is browser-only. For same-origin URLs it
fetches the HTML page, updates `history.pushState()` or
`history.replaceState()`, swaps the document, and dispatches
`koze:navigation`. If the fetch fails or does not return HTML,
Koze falls back to normal `location.assign()` /
`location.replace()` navigation.

To refetch the current route after a mutation without pushing a new
history entry, use `refreshRoute()`:

```html
<script>
  import { refreshRoute } from 'koze:navigation';

  async function saveDraft() {
    await saveDraftRpc();
    await refreshRoute();
  }
</script>
```

`refreshRoute()` uses the same same-origin HTML navigation pipeline as
`navigateTo()`, but replaces the current history entry.

## Server module helpers

Inside `src/server/*.ts` modules, prefer the server-capable `koze:*` modules:

```ts
import { request, url, headers, params, locals } from 'koze:request';
import { redirect } from 'koze:navigation';
import { cookies } from 'koze:cookies';
```

Use `cloudflare:workers` directly for `env`.

`locals` is the single request-scoped scratchpad server code reads after middleware writes to `ctx.locals`:

```ts
// src/middleware.ts
auth: {
  async request(ctx, next) {
    ctx.locals.user = await loadUser(ctx);
    return next();
  },
},
```

```ts
// src/server/items.ts
import { locals } from 'koze:request';

export async function listItems() {
  const { user } = locals as App.Locals & { user: { id: string } | null };
  if (!user) return [];
  // …
}
```

Type `App.Locals` in `src/app.d.ts` to get autocomplete on `locals`:

```ts
declare global {
  namespace App {
    interface Locals {
      user: { id: string; email: string } | null;
    }
  }
}
export {};
```

## Cookies

Server modules can read and write cookies through `koze:cookies`:

```ts
import { cookies } from 'koze:cookies';

const theme = cookies.get('theme');
cookies.set('theme', 'dark', { path: '/', sameSite: 'Lax' });
cookies.delete('theme', { path: '/' });
```

## Environment helpers

### `dev` — compile-time environment flag

```ts
import { dev } from 'koze:environment';
```

- During `vite dev` → `dev` evaluates to `true`
- During `vite build` → `dev` evaluates to `false`

Because `dev` is compiled to a literal boolean, the unused branch in any `if (dev) { … }` block tree-shakes out of the production worker. Use it to bypass auth gates locally, log verbosely, or inline dev-only stubs:

```ts
import { dev } from 'koze:environment';

defineMiddleware({
  auth: dev
    ? { async request(_ctx, next) { return next(); } } // dev bypass
    : kyzenAuthMiddleware(authConfig),
});
```

`dev` is safe to import from:

- Route `<script>` blocks
- `src/server/*.ts` modules
- `src/middleware.ts`

### Worker bindings (`env`)

For D1, KV, R2, Durable Object namespaces, secrets, and any other Cloudflare binding, read from `cloudflare:workers`:

```ts
import { env } from 'cloudflare:workers';

const turnstileSiteKey = env.TURNSTILE_SITE_KEY ?? '';
```

Treat binding access as **server-only**. Templates and client scripts should receive precomputed values — pass `turnstileSiteKey` as a prop or render it into the template, don't import `env` into a route's top `<script>` (the compiler will strip the import on the client side anyway, but doing it explicitly keeps the data flow obvious).

## Read next

- [Middleware](/docs/koze/middleware) — the request lifecycle, hooks, and execution order
- [Routing](/docs/koze/routing) — file-based routing, params, layouts
- [Configuration](/docs/koze/configuration) — where each kind of config lives
