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:
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:
app.on("GET", "/users", handler);
app.on("POST", "/users", handler);Catch-All Method
Register a handler for all HTTP methods on a path:
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:
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:
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():
// 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:
// 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:
// 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:
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:
// 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 reachedStatic Routes vs Dynamic Routes
Kyrin optimizes static routes (paths without parameters) with O(1) lookup:
// 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 different paths. If you want to handle both:
app.get("/users", handler);
app.get("/users/", handler);
// Or use a redirect
app.get("/users/", (c) => c.redirect("/users", 301));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:
// 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.
Complete Example
import { Kyrin, Router } from "kyrin";
// 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") }));
api.post("/users", async (c) => {
const body = await c.body<{ name: string }>();
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);