A step-by-step walkthrough covering everything KosmoJS provides.
Create a Project
npm create kosmo demoA short interactive setup creates the project together with your first source folder, prompting for the framework and backend.
The folder itself defaults to app at base / (override later, or via flags in CLI mode).
It is also possible to bootstrap in the current folder, just use . as name:
npm create kosmo .Prefer a scripted setup?
Provide the framework/backend up front and no prompts appear:
npm create kosmo demo -- --frontend solid --backend honoThe first source folder is always app, serving pages at / and its API at /api. Both frontend and backend can be further configured in kosmo.config.ts.
Need no backend? Provide --no-backend flag:
npm create kosmo demo -- --frontend solid --no-backendSame for the frontend, provide the --no-frontend flag to get a backend-only setup.
After bootstrap, cd into freshly created project (unless the project was bootstrapped in the current folder):
cd ./demoInstall Dependencies
npm installThe scaffold is complete, not a skeleton - everything comes filled in. What it doesn't have yet is routes, and that is what you add next.
Start the dev server
The dev server watches your routes and recomputes as you work - and seeds starter code into any route or page file you create empty:
npm run devYour app is now running at http://localhost:4556.
Create Your First API Route
Create api/users/[id]/index.ts - KosmoJS detects the file and seeds boilerplate:
// Hono: api/users/[id]/index.ts
import { defineRoute } from "_/api";
export default defineRoute<"users/[id]">(({ GET }) => [
GET(async (ctx) => {
return ctx.text("users/[id] route starts here - replace this response with real logic.");
}),
]);Some editors show seeded content immediately; others need a brief unfocus/refocus.
Replace with real logic:
// Hono: api/users/[id]/index.ts
import { defineRoute } from "_/api";
export default defineRoute<"users/[id]">(({ GET }) => [
GET(async (ctx) => {
const { id } = ctx.req.param();
const user = { id, name: "Jane Smith", email: "jane@example.com" };
return ctx.json(user);
}),
]);With dev server running, visit http://localhost:4556/api/users/123:
Add Validation
Parameter Validation
Pass a tuple as the second type argument to refine params. Each position maps to a route parameter in order. Validation works identically across all frameworks, read validated params via *.validated.params:
type User = { id: number; name: string; email: string }
export default defineRoute<"users/[id]", [
number
]>(({ GET }) => [
GET(async (ctx) => {
const { id } = ctx.validated.params; // id is a validated number
// ...
}),
]);Use VRefine for additional constraints (no import needed):
defineRoute<"users/[id]", [
VRefine<number, { minimum: 1, multipleOf: 1 }> // positive integer
]>Raw params still can be accessed via ctx.req.param()/event.context.params/ctx.params, but they are untyped strings - prefer ctx.validated.params.
Payload/Response Validation
The first type argument to each method handler defines validation targets.
Metadata targets (any method): query · headers · cookies
Body targets (mutually exclusive, POST/PUT/PATCH only): json · form · raw
// Hono: api/users/index.ts
import type { CreateUserPayload, User } from "./types";
export default defineRoute<"users">(({ POST }) => [
POST<{
json: CreateUserPayload,
response: [200, "json", User]
}>(async (ctx) => {
const { name, email, age } = ctx.validated.json;
return ctx.json({ id: 1, name, email, age });
}),
]);export type CreateUserPayload = {
name: string;
email: VRefine<string, { format: "email" }>;
age?: number;
}
export type User = { id: number; name: string; email: string }Payload is validated before your handler runs. Response is validated before it's sent.
Add Middleware
For simple cases, wire middleware inline with use:
import { logRequest } from "~/middleware/logging";
export default defineRoute<"users/[id]">(({ use, GET }) => [
use(logRequest),
GET(async (ctx) => { /* ... */ }),
]);For anything shared across routes, use cascading middleware instead. Create api/users/use.ts - it wraps every route under /api/users automatically:
import { use } from "_/api";
export default [
use(async (ctx, next) => {
// runs for every route under /api/users
return next();
})
];No imports in route files, no repetition. Parent use.ts files wrap child routes automatically.
Fetch Clients
Fetch clients are fully typed and validated client-side using the same high-performance TypeBox validators as the server - identical results, no duplication, no drift.
Invalid requests are caught before they leave the browser:
// React: pages/users/[id]/index.tsx
import { useState, useEffect } from "react";
import { useParams } from "react-router";
import fetchClients from "_/fetch";
const { GET } = fetchClients["users/[id]"];
export default function UserPage() {
const params = useParams();
const [user, setUser] = useState(null);
useEffect(() => { GET([params.id]).then(setUser); }, [params.id]);
// ...
}Server-side validation still runs even when endpoints are called directly - client validation is additive, not a substitute.
Create Client Pages
Pages live in pages/ and follow the same directory-based routing as API routes. Create pages/users/index.tsx - KosmoJS seeds framework-specific boilerplate.
Add a layout for shared UI across route groups - create pages/users/layout.tsx:
pages/
└── users/
├── layout.tsx ← wraps all pages under /users
└── index.tsxLayouts can be nested - deeper layouts wrap inner layouts, matching your route hierarchy.
Server-Side Rendering
Every folder has it on already. To turn it off, or back on, edit kosmo.config.ts:
import { defineConfig } from "@kosmojs/dev";
export default defineConfig({
frontend: {
// ...
ssr: true,
},
});Restart dev server after changing
kosmo.config.ts.
KosmoJS seeds entry/server.ts - your SSR orchestration file. Critical CSS is extracted and inlined automatically; remaining styles load asynchronously.
See it running - pnpm preview builds and serves the production output, and rebuilds whenever you save:
pnpm previewFor deployment, pnpm build writes dist/run.js, which serves every source folder from one process. Folders are also bundled separately, so they can be deployed, scaled and run independently when that suits you better.
Details › · Dev / Build / Run ›
Add More Source Folders
Your project starts with the source folder created at bootstrap. As the app grows, add more - one per distinct concern (main app, admin panel, marketing site, etc.).
Each is independent with its own set of frameworks, config, base URL, etc.
npm run folderYou'll be prompted for the frontend, backend, and more. The name gives the folder its prefixes: pages at /<name>, API at /<name>/api.
Non-interactive mode is also supported; pass any flag and no prompts appear:
--frontend solid|react|vue|svelte|mdxor--no-frontend(one required)--backend hono|h3|koaor--no-backend(one required)--overwriteto proceed into a non-empty directory
Everything else - SSR, SSG, TanStack Query - is a key in the folder's kosmo.config.ts, not a flag at creation.
npm run folder -- <name> --frontend solid --backend honoNeed no backend? Provide the --no-backend flag for a frontend-only folder (a static docs or marketing site).
Need no client? Provide the --no-frontend flag for a backend-only folder (an API service with no UI).
The choice is always explicit - a forgotten flag is an error, never a silent default:
npm run folder -- api --backend hono --no-frontend # API only, no UI
npm run folder -- docs --frontend mdx --no-backend # UI only, no backendCreating a source folder adds framework-specific dependencies. Install them:
npm installDirectory-Based Routing
Folder names become URL segments. Each route requires an index file:
api/
users/
index.ts -> /api/users
[id]/
index.ts -> /api/users/:id
pages/
users/
index.tsx -> /users
[id]/
index.tsx -> /users/:idParameters: [id] required · {id} optional · {...path} splat. Same pattern for API and pages - learn once, use everywhere.
Path Mappings
Your project starts with a minimal tsconfig.json:
{
"extends": "./lib/tsconfig.json",
"include": ["./lib/*.d.ts"]
}The extended config provides path mappings used throughout the framework. You can add your own paths, but these prefixes are reserved:
@/*- Root-level imports~/*- Source folder imports_/*- Derived code imports
Project Structure › walks the whole layout - what lives in src/ versus lib/, and what each alias resolves to.
Next Steps
Core patterns: Routing · Validation · Middleware · Layouts · Fetch Clients
Advanced: VRefine · OpenAPI · Production Builds