Request APIs | Koze | Primitives Docs

Request APIs

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

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.

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>:

<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:

<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:

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

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

<button>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():

<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:

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:

// src/middleware.ts
auth: {
  async request(ctx, next) {
    ctx.locals.user = await loadUser(ctx);
    return next();
  },
},
// 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:

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

Cookies

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

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

import { dev } from 'koze:environment';
  • During vite devdev evaluates to true
  • During vite builddev 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:

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:

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).

  • Middleware — the request lifecycle, hooks, and execution order
  • Routing — file-based routing, params, layouts
  • Configuration — where each kind of config lives