Skip to content

Zero to a working route in under five minutes.

asciicast

Create & install ​

sh
npm create kosmo
cd my-app
npm install
sh
pnpm create kosmo
cd my-app
pnpm install
sh
yarn create kosmo
cd my-app
yarn install

Add a source folder ​

sh
npm run +folder
sh
pnpm +folder
sh
yarn +folder

You'll be prompted for a folder name, base URL, framework, and backend.

Install newly added dependencies:

sh
npm install
sh
pnpm install
sh
yarn install

Start the dev server ​

sh
npm run dev
sh
pnpm dev
sh
yarn dev

Create a route ​

Create the file api/users/[id]/index.ts - KosmoJS detects it and generates starter code automatically.

Replace the generated content with something real:

ts
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;
    ctx.body = { id: Number(id), name: "Jane Smith", email: "jane@example.com" };
  }),
]);
ts
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();
    ctx.json({ id: Number(id), name: "Jane Smith", email: "jane@example.com" });
  }),
]);

Visit http://localhost:4556/api/users/123. You should see JSON.

Create a page ​

With the dev server still running, create pages/users/[id]/index.tsx (or .vue). KosmoJS generates a placeholder component - replace it with a page that fetches from your API route:

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 { id } = useParams();
  const [user, setUser] = useState(null);

  useEffect(() => { GET([id]).then(setUser); }, [id]);

  return user
    ? <div><h1>{user.name}</h1><p>{user.email}</p></div>
    : <div>Loading...</div>;
}
tsx
import { useParams } from "@solidjs/router";
import { 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]));

  return user()
    ? <div><h1>{user().name}</h1><p>{user().email}</p></div>
    : <div>Loading...</div>;
}
vue
<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>

<template>
  <div v-if="user"><h1>{{ user.name }}</h1><p>{{ user.email }}</p></div>
  <div v-else>Loading...</div>
</template>

Visit http://localhost:4556/users/123. Your page renders with data from the API.

The fetch client is fully typed - user.name and user.email autocomplete in your editor, and invalid parameters are caught before the request leaves the browser.

What just happened ​

Your folder structure became your routes:

api/users/[id]/index.ts     ➜  /api/users/:id
pages/users/[id]/index.tsx  ➜  /users/:id

[id] is a required parameter. {id} makes it optional. {...path} matches any depth. The parallel structure between api/ and pages/ is intentional - API endpoints and their corresponding pages are always easy to find.

The fetch client was generated automatically from your API route definition. Change the API types, and the client updates with them - no manual sync.


That's the foundation. From here:

  • Tutorial - validation, middleware, fetch clients, pages, SSR
  • Routing - parameters, mixed segments, power syntax
  • Features - everything KosmoJS provides, at a glance

Released under the MIT License.