Handler
Each route handler receives a Context object (usually called c) that gives you access to request data and response helpers.
The Context Object
app.get("/example", async (c) => {
// c is your Context — it has everything you need
});Request Properties
Access basic request info directly:
app.get("/info", (c) => {
return {
method: c.method, // "GET", "POST", etc.
path: c.path, // "/info"
headers: c.headers, // Headers object
};
});Path Parameters
Get dynamic path segments with c.param():
// GET /users/42
app.get("/users/:id", (c) => {
const id = c.param("id"); // "42"
return { id };
});
// GET /posts/123/comments/456
app.get("/posts/:postId/comments/:commentId", (c) => {
const postId = c.param("postId"); // "123"
const commentId = c.param("commentId"); // "456"
return { postId, commentId };
});Returns null if the parameter doesn't exist.
Query Parameters
Get URL query string values with c.query():
// GET /search?q=kyrin&limit=10
app.get("/search", (c) => {
const q = c.query("q"); // "kyrin"
const limit = c.query("limit"); // "10"
const page = c.query("page"); // null (not present)
return { q, limit };
});Headers
Read request headers:
app.get("/auth", (c) => {
const auth = c.header("Authorization");
const contentType = c.header("Content-Type");
if (!auth) {
return c.json({ error: "No auth header" }, 401);
}
return { auth };
});The c.headers property gives you the full Headers object if you need it.
Body Parsing
JSON Body
Parse JSON request body with type safety:
app.post("/users", async (c) => {
// With type parameter
const body = await c.body<{ name: string; email: string }>();
return { received: body };
});
// Or destructure directly
app.post("/users", async (c) => {
const { name, email } = await c.body<{ name: string; email: string }>();
return { name, email };
});Constraints
Use c.body() and define (c: any) to parse JSON request body with flexible type:
app.post("/users", async (c:any) => {
const body = await c.body();
return { received: body };
});
app.post("/users", async (c: any) => {
const body = await c.body();
const response = `Hello ${body.name}!`;
return { message: response };
});
// minimal version
app.post("/users", async (c: any) => ({
message: `Hello ${(await c.body()).name}!`,
}));Force await and async function to do this!!
Plain Text
Get raw text body:
app.post("/webhook", async (c) => {
const text = await c.text();
return { length: text.length };
});Form Data
Handle form submissions:
app.post("/upload", async (c) => {
const form = await c.formData();
const name = form.get("name");
const file = form.get("file") as File;
return {
name,
filename: file?.name,
size: file?.size,
};
});Response Helpers
JSON Response
Return JSON with optional status code:
app.get("/data", (c) => {
return c.json({ message: "Hello" });
});
app.get("/error", (c) => {
return c.json({ error: "Something went wrong" }, 500);
});Plain Text
Send plain text:
app.get("/text", (c) => {
return c.send("Hello, plain text!");
});
app.get("/error", (c) => {
return c.send("Error occurred", 500);
});HTML
Return HTML content:
app.get("/page", (c) => {
return c.html("<h1>Welcome</h1><p>This is HTML</p>");
});
app.get("/error", (c) => {
return c.html("<h1>404</h1><p>Not found</p>", 404);
});Redirect
Redirect to another URL:
app.get("/old-page", (c) => {
return c.redirect("/new-page"); // 302 by default
});
app.get("/permanent", (c) => {
return c.redirect("/new-location", 301); // Permanent redirect
});Not Found
Return a 404 response:
app.get("/users/:id", (c) => {
const user = findUser(c.param("id"));
if (!user) {
return c.notFound();
}
return user;
});Setting Response Headers
Use c.set.headers to add headers to the response:
app.get("/data", (c) => {
c.set.headers["X-Request-Id"] = "abc123";
c.set.headers["Cache-Control"] = "max-age=3600";
return { data: "with custom headers" };
});Setting Status Code
Set the status code before returning:
app.post("/users", async (c) => {
const body = await c.body();
// ... create user
c.set.status = 201; // Created
return { id: 1, ...body };
});Or pass it directly to response helpers:
app.post("/users", async (c) => {
const body = await c.body();
return c.json({ id: 1, ...body }, 201);
});The Store
The store is a simple object for passing data between middleware and handlers:
// Middleware sets data
app.use(async (c, next) => {
c.store.requestId = crypto.randomUUID();
c.store.startTime = Date.now();
await next();
});
// Handler reads it
app.get("/data", (c) => {
return {
requestId: c.store.requestId,
data: "something",
};
});TypeScript users: store is typed as Record<string, unknown>, so you may need to cast values.
Raw Request
Access the underlying Request object when you need it:
app.post("/raw", async (c) => {
const contentLength = c.req.headers.get("Content-Length");
const arrayBuffer = await c.req.arrayBuffer();
return { size: arrayBuffer.byteLength };
});Complete Example
import { Kyrin } from "kyrin";
const app = new Kyrin();
// Simple GET
app.get("/", () => ({ message: "Hello!" }));
// Path params
app.get("/users/:id", (c) => {
const id = c.param("id");
return { userId: id };
});
// Query params
app.get("/search", (c) => {
const q = c.query("q") || "";
const page = parseInt(c.query("page") || "1", 10);
return { query: q, page };
});
// JSON body
app.post("/users", async (c) => {
const { name, email } = await c.body<{ name: string; email: string }>();
c.set.status = 201;
return { id: 1, name, email };
});
// Custom headers
app.get("/download", (c) => {
c.set.headers["Content-Disposition"] = "attachment; filename=data.json";
return { data: "example" };
});
// Redirect
app.get("/github", (c) => {
return c.redirect("https://github.com");
});
// 404
app.get("/maybe/:id", (c) => {
const id = c.param("id");
if (id === "nothing") {
return c.notFound();
}
return { found: id };
});
app.listen(3000);