Skip to content

Routes ​

Routing is how you map URLs to handler functions. Kyrin uses a radix tree internally, which means route matching is fast — O(k) where k is the path length.

Basic Routes ​

Register routes using HTTP method shortcuts:

typescript
app.get("/users", handler);
app.post("/users", handler);
app.put("/users/:id", handler);
app.patch("/users/:id", handler);
app.delete("/users/:id", handler);
app.options("/users", handler);
app.head("/users", handler);

Or use the generic on() method:

typescript
app.on("GET", "/users", handler);
app.on("POST", "/users", handler);

Catch-All Method ​

Register a handler for all HTTP methods on a path:

typescript
app.all("/health", () => ({ status: "ok" }));

This registers the same handler for GET, POST, PUT, DELETE, PATCH, OPTIONS, and HEAD.

Path Parameters ​

Use :name syntax to capture dynamic segments:

typescript
app.get("/users/:id", (c) => {
  const userId = c.param("id");
  return { userId };
});

app.get("/posts/:postId/comments/:commentId", (c) => {
  const postId = c.param("postId");
  const commentId = c.param("commentId");
  return { postId, commentId };
});

Parameters are always strings. Parse them as needed:

typescript
app.get("/users/:id", (c) => {
  const id = parseInt(c.param("id")!, 10);
  if (isNaN(id)) {
    return c.json({ error: "Invalid ID" }, 400);
  }
  // ...
});

Query Parameters ​

Access query string values using c.query():

typescript
// GET /search?q=hello&page=2
app.get("/search", (c) => {
  const query = c.query("q"); // "hello"
  const page = c.query("page"); // "2"
  return { query, page };
});

Query parameters are also strings (or null if not present).

Route Groups (Mounting Routers) ​

For larger apps, split routes into separate routers and mount them with a prefix:

Basic user.ts Routes File:

typescript
// src/routes/user.ts
import { Router } from "kyrin"

export const userRouter = new Router()

userRouter.get('/',()=>{
	return {"message":"Hello User Kyrin"}
})

userRouter.get('/:id',(c)=>{
	return {"message":"User Found"}
})

userRouter.post('/create',(c)=>{
	return {"message":"User Created"}
})

Main File index.ts:

typescript
// src/index.ts
import { Kyrin } from "kyrin"
import { userRouter } from "./routes/user";

const app = new Kyrin()

// Mouted them
app.route("/users", userRouter); // /users, /users/:id

app.listen(3000);

This keeps your code organized and makes route handlers reusable.

Guarded Routes ​

Use guard() to wrap a group of routes with a middleware:

typescript
const auth = async (c:any, next:any) => {
  const token = c.header("Authorization");
  if (!token) {
    return c.json({ error: "Unauthorized" }, 401);
  }
  // Validate token, set user...
  c.store.user = { id: 1, name: "John" };
  await next();
};

app.guard(auth, (app) => {
  app.get("/profile", (c:any) => c.store.user);
  app.get("/settings", (c:any) => ({ user: c.store.user }));
});

Routes inside guard() will only execute if the middleware calls next().

Route Matching Order ​

Routes are matched in the order they were registered. More specific routes should be registered before generic ones:

typescript
// Good — specific first
app.get("/users/me", () => "Current user");
app.get("/users/:id", (c) => `User ${c.param("id")}`);

// Bad — generic catches "me" as an ID
app.get("/users/:id", (c) => `User ${c.param("id")}`);
app.get("/users/me", () => "Current user"); // Never reached

Static Routes vs Dynamic Routes ​

Kyrin optimizes static routes (paths without parameters) with O(1) lookup:

typescript
// O(1) lookup — cached in a Map
app.get("/", handler);
app.get("/about", handler);
app.get("/contact", handler);

// O(k) lookup — uses radix tree
app.get("/users/:id", handler);
app.get("/posts/:id/comments", handler);

You don't need to do anything special — the framework handles this automatically.

Trailing Slashes ​

Kyrin treats /users and /users/ as the same path (both match the route). You don't need to handle them separately:

typescript
app.get("/users", handler);

// Both URLs work:
// GET /users    -> matches
// GET /users/   -> also matches

If you need strict trailing slash handling, use explicit routes with redirects:

typescript
// Redirect /users/ to /users
app.get("/users/", (c) => c.redirect("/users", 301));
app.get("/users", handler);

404 Handling ​

When no route matches, Kyrin returns a plain "Not Found" response with status 404. To customize this, add a catch-all route at the end:

typescript
// Register all your routes first...
app.get("/", handler);
app.get("/about", handler);

// Then add a fallback
app.all("/*", (c) => {
  return c.json({ error: "Not found", path: c.path }, 404);
});

Note: Wildcard (*) matching is supported for catch-all patterns.

Request Validation ​

Validate request body using Zod schemas:

typescript
import { z } from "zod";

const userSchema = z.object({
  name: z.string().min(2),
  email: z.string().email(),
  age: z.number().optional()
});

// Pass schema as 2nd argument
app.post("/users", userSchema, (c) => {
  const body = c.body(); // Already validated & typed
  return c.json({ created: body.name });
});

On validation failure, Kyrin automatically returns a 400 Bad Request response with error details.

The schema provides automatic TypeScript type inference:

typescript
app.post("/users", userSchema, (c) => {
  const data = c.body();
  console.log(data.name);  // TypeScript knows this is string
  console.log(data.email); // TypeScript knows this is string
  console.log(data.age);   // TypeScript knows this is number | undefined
});

Complete Example ​

typescript
import { Kyrin, Router } from "kyrin";
import { z } from "zod";

// API routes
const api = new Router();
api.get("/health", () => ({ status: "ok" }));
api.get("/users", () => [{ id: 1, name: "Alice" }]);
api.get("/users/:id", (c) => ({ id: c.param("id") }));

// With validation
const userSchema = z.object({ name: z.string() });
api.post("/users", userSchema, (c) => {
  const body = c.body();
  return { created: body.name };
});

// App
const app = new Kyrin();

// Mount API
app.route("/api/v1", api);

// Static pages
app.get("/", () => "Welcome!");
app.get("/about", () => "About us");

// 404 fallback
app.all("/*", (c) => c.json({ error: "Not found" }, 404));

app.listen(3000);

Released under the MIT License.