A step-by-step walkthrough covering everything KosmoJS provides.
π Create Your Project β
npm create kosmo
# non-interactive: npm create kosmo --name my-apppnpm create kosmo
# non-interactive: pnpm create kosmo --name my-appyarn create kosmo
# non-interactive: yarn create kosmo --name my-appcd ./my-appπ¦ Install Dependencies β
npm installpnpm installyarn installπ Create a Source Folder β
KosmoJS doesn't create a source folder automatically - you add them as needed, 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 +folderpnpm +folderyarn +folderYou'll be prompted for folder name, base URL, framework, backend, and SSR/SSG.
Non-interactive mode is also supported:
--name--base--framework solid|react|vue|mdx--backend koa|hono--ssr--ssg
npm run +folder -- --name front --base / --framework solid --backend koapnpm +folder --name front --base / --framework solid --backend koayarn +folder --name front --base / --framework solid --backend koaCreating a source folder adds framework-specific dependencies to package.json. Install them:
npm installpnpm installyarn installπ£οΈ Directory-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.base.json" }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_/*- Generated code imports
β Create Your First API Route β
Create api/users/[id]/index.ts - KosmoJS detects the file and generates boilerplate:
import { defineRoute } from "_/api";
export default defineRoute<"users/[id]">(({ GET }) => [
GET(async (ctx) => {
ctx.body = "Automatically generated route: [ users/[id] ]"
}),
]);import { defineRoute } from "_/api";
export default defineRoute<"users/[id]">(({ GET }) => [
GET(async (ctx) => {
ctx.text("Automatically generated route: [ users/[id] ]");
}),
]);Some editors show generated content immediately; others need a brief unfocus/refocus.
Replace with real logic:
import { defineRoute } from "_/api";
type User = { id: number; name: string; email: string }
export default defineRoute<"users/[id]">(({ GET }) => [
GET(async (ctx) => {
const { id } = ctx.params;
const user: User = { id: Number(id), name: "Jane Smith", email: "jane@example.com" };
ctx.body = user;
}),
]);import { defineRoute } from "_/api";
type User = { id: number; name: string; email: string }
export default defineRoute<"users/[id]">(({ GET }) => [
GET(async (ctx) => {
const { id } = ctx.req.param();
const user: User = { id: Number(id), name: "Jane Smith", email: "jane@example.com" };
ctx.json(user);
}),
]);Start the dev server and visit http://localhost:4556/api/users/123:
npm run devpnpm devyarn devπ‘οΈ Add Validation β
Parameter Validation β
Pass a tuple as the second type argument to refine params. Each position maps to a route parameter in order:
export default defineRoute<"users/[id]", [
number
]>(({ GET }) => [
GET(async (ctx) => {
const { id } = ctx.validated.params; // number, not string
const user: User = { id, name: "Jane Smith", email: "jane@example.com" };
ctx.body = user;
}),
]);export default defineRoute<"users/[id]", [
number
]>(({ GET }) => [
GET(async (ctx) => {
const { id } = ctx.validated.params; // number, not string
const user: User = { id, name: "Jane Smith", email: "jane@example.com" };
ctx.json(user);
}),
]);Use VRefine for additional constraints (no import needed):
defineRoute<"users/[id]", [
VRefine<number, { minimum: 1, multipleOf: 1 }> // positive integer
]>ctx.params/ctx.req.param() still exist but return 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/DELETE only): json Β· form Β· raw
type CreateUserPayload = {
name: string;
email: VRefine<string, { format: "email" }>;
age?: number;
}
export default defineRoute<"users">(({ POST }) => [
POST<{
json: CreateUserPayload,
response: [200, "json", User]
}>(async (ctx) => {
const { name, email, age } = ctx.validated.json;
ctx.body = { id: 1, name, email, age };
}),
]);type CreateUserPayload = {
name: string;
email: VRefine<string, { format: "email" }>;
age?: number;
}
export default defineRoute<"users">(({ POST }) => [
POST<{
json: CreateUserPayload,
response: [200, "json", User]
}>(async (ctx) => {
const { name, email, age } = ctx.validated.json;
ctx.json({ id: 1, name, email, age });
}),
]);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:
import { useParams, createAsync } from "@solidjs/router";
import fetchClients from "_/fetch";
const { GET } = fetchClients["users/[id]"];
export default function UserPage() {
const params = useParams();
const user = createAsync(() => GET([params.id]));
// ...
}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]);
// ...
}<script setup lang="ts">
import { ref, onMounted } from "vue";
import { useRoute } from "vue-router";
import fetchClients from "_/fetch";
const { GET } = fetchClients["users/[id]"];
const route = useRoute();
const user = ref(null);
onMounted(async () => { user.value = await GET([route.params.id]); });
</script>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 generates 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 β
Enable when creating a source folder (--ssr), or add it later in kosmo.config.ts:
import { defineConfig, ssrGenerator } from "@kosmojs/dev";
export default defineConfig({
generators: [
// ...
ssrGenerator(),
]
});Restart dev server after adding new generators.
KosmoJS generates entry/server.ts - your SSR orchestration file. Critical CSS is extracted and inlined automatically; remaining styles load asynchronously.
Build and run:
pnpm build
node dist/front/ssr/server.js -p 4556The API server and SSR server are bundled separately - deploy, scale, and run them independently.
ποΈ Multiple Source Folders β
Add more source folders as your project grows - each with its own framework stack, base URL, and build output. They develop and deploy independently but share types, validation logic, and infrastructure.
pnpm +folder # add a new source folder
pnpm dev # runs all source folders in parallel
pnpm build admin # build a specific folderNext Steps β
Core patterns: Routing Β· Validation Β· Middleware Β· Layouts Β· Fetch Clients
Advanced: VRefine Β· OpenAPI Β· Production Builds