Getting Started
Install koze, koze/vite, and boot a first app on Cloudflare Workers
Install
Koze is Vite-first. Every new project runs through vite dev / vite build with the Koze plugin.
Scaffold with koze
npx koze create my-app
cd my-app
bun install
bun run dev
The scaffolder drops a working Vite project with routes, assets, and a wrangler.jsonc pointed at src/worker.ts.
Or install into an existing project
npm install @kuratchi/koze
npm install -D vite wrangler @cloudflare/workers-types
Optional packages (install only what you need):
| Package | When |
|---|---|
kunii |
D1 / Durable Object SQLite schema + query builder |
kyzen |
Credentials, sessions, OAuth, RBAC, Turnstile |
kuzan |
Ready-made HTML component library |
tailwindcss @tailwindcss/vite |
Tailwind CSS (plain Vite plugin) |
lightningcss |
CSS transformer (configure via vite.config.ts) |
Minimal project shape
my-app/
├─ package.json
├─ vite.config.ts
├─ wrangler.jsonc
├─ tsconfig.json
└─ src/
├─ worker.ts export { default } from 'koze:worker'
├─ middleware.ts defineMiddleware({ ... })
├─ app.koze document shell
├─ app.css global stylesheet (optional)
├─ assets/ static files served verbatim at /
├─ server/ auto-discovered DOs / workflows / queues / pipelines / containers
└─ routes/
├─ layout.koze shared fragment
└─ index.koze /
There is no project-level config file. Build-time options (security headers, directory overrides) are passed to the koze() Vite plugin. Request-time concerns (auth, ORM auto-migration, custom steps) live in src/middleware.ts.
For a full reference of every special file, see Project structure.
vite.config.ts
The Koze plugin owns routing, virtual modules, and Wrangler sync. Nothing else to configure for the common case.
import { defineConfig } from 'vite';
import { koze } from '@kuratchi/koze/vite';
export default defineConfig({
plugins: [koze()],
});
wrangler.jsonc
{
"name": "my-app",
"main": "src/worker.ts",
"compatibility_date": "2026-01-01",
"compatibility_flags": ["nodejs_compat"],
"assets": { "directory": "src/assets" }
}
The Koze plugin auto-maintains durable_objects, containers, queues.consumers, pipelines, and migrations[] entries based on what it discovers in src/server/. Do not hand-edit those fields.
package.json scripts
{
"scripts": {
"dev": "vite",
"build": "vite build",
"deploy": "vite build && wrangler deploy"
}
}
src/worker.ts
export { default } from 'koze:worker';
That's the whole file. The virtual module synthesizes fetch + queue handlers, Durable Object exports, and any discovered convention classes.
src/app.koze — document shell
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>My App</title>
</head>
<body>
<slot></slot>
</body>
</html>
Optional — the framework provides a minimal default shell when absent. Don't add a <link> for your global CSS; Koze injects it (see next section).
src/routes/layout.koze — shared fragment
Wraps every page. Not a document — no <!DOCTYPE>, no <html>.
<script>
import { url } from 'koze:request';
const current = url.pathname;
</script>
<header>
<a href="/" class={current === '/' ? 'active' : ''}>Home</a>
<a href="/about" class={current === '/about' ? 'active' : ''}>About</a>
</header>
<main>
<slot></slot>
</main>
src/routes/index.koze — first page
<script>
const items = ['Alpha', 'Bravo', 'Charlie'];
</script>
<h1>Hello Koze</h1>
if (items.length > 0) {
<ul>
for (const item of items) {
<li>{item}</li>
}
</ul>
} else {
<p>No items yet.</p>
}
The page is server-rendered. The top <script> runs during SSR; $server/* imports become RPC stubs when the same code runs in the browser.
First stylesheet — src/app.css
Drop a file at src/app.css. Koze registers it as a Vite client entry, runs every CSS plugin on it (Tailwind, PostCSS, CSS Modules, Lightning CSS), and auto-injects <link rel="stylesheet"> into your app shell before </head>. You don't write the <link> tag.
/* src/app.css */
body {
margin: 0;
font-family: system-ui, sans-serif;
}
That's the full setup. Add Tailwind by installing @tailwindcss/vite, dropping it into vite.config.ts, and @import "tailwindcss" from src/app.css. See Styling for Tailwind, CSS frameworks, and static asset conventions.
First browser-side JavaScript
Koze routes have one script block. Declare a handler in the top
<script> and bind it with onclick={fn(args)}:
<script>
import { deleteItem } from '$server/items';
async function confirmDelete(id) {
if (!confirm('Delete?')) return;
await deleteItem(id);
location.reload();
}
</script>
<ul>
for (const item of items) {
<li>
{item.title}
<button type="button">Delete</button>
</li>
}
</ul>
The same script runs on the server (resolving the await) and in the
browser (wiring up confirmDelete). No addEventListener, no
document.querySelector, no typeof document guards.
See Client interactivity for the event-handler reference, SSR data hydration, and browser-only DOM patterns inside the single route script.
Run locally
bun install
bun run dev
vite dev serves the app on http://localhost:5173 with the Cloudflare Vite plugin providing a Workers-accurate runtime.
What to read next
- Project structure — every special file reference
- Configuration — where each kind of config lives
- Routing — dynamic segments, template syntax, components
- Actions — native form POSTs with server functions
- Middleware — request lifecycle, hooks, composition
- Request APIs —
koze:request, redirects, env helpers - ORM Getting Started — schemas, migrations, queries
- Auth Getting Started — credentials, sessions, OAuth, RBAC