Routing
Express-inspired router with path parameters, wildcards, and nested routes.
Route Definition
const routes: []const tk.Route = &.{
.get(path, handler),
.post(path, handler),
.put(path, handler),
.delete(path, handler),
.patch(path, handler),
.head(path, handler),
.options(path, handler),
};Body-less variants (skip JSON parsing):
.post0(path, handler)
.put0(path, handler)
.patch0(path, handler)Path Parameters
Syntax: /:param
Maximum: 16 parameters per route.
.get("/users/:id", handler)
.get("/users/:userId/posts/:postId", handler)Parameters are passed as function arguments after dependencies:
fn handler(db: *Database, userId: []const u8, postId: []const u8) !Post {
return db.getPost(userId, postId);
}Wildcards
Syntax: *
.get("/assets/*", handler)
.get("/api/*", handler)Grouping
.group(prefix, routes)Groups routes under a common prefix:
const routes = &.{
.group("/api", &.{
.get("/users", getUsers),
.post("/users", createUser),
}),
};Router DSL
.router(T)Creates routes from a struct's public functions. Function names define method and path:
const api = struct {
pub fn @"GET /"() []const u8 { ... }
pub fn @"GET /:id"(id: u32) !User { ... }
pub fn @"POST /"(body: User) !User { ... }
pub fn @"PUT /:id"(id: u32, body: User) !void { ... }
pub fn @"DELETE /:id"(id: u32) !void { ... }
};
const routes = &.{ .router(api) };Scoped Dependencies
.provide(fn, routes)Inject dependencies to nested routes (middleware pattern):
const routes = &.{
.provide(authenticate, &.{
.get("/profile", getProfile),
.post("/logout", logout),
}),
};
fn authenticate(req: *tk.Request) !*User {
const token = req.header("Authorization") orelse return error.Unauthorized;
return try validateToken(token);
}
fn getProfile(user: *User) !UserProfile {
return .{
.id = user.id,
.name = user.name,
.email = user.email,
};
}The user dependency is automatically available to nested route handlers.
Route Helpers
Static Responses
tk.Route.send(value)Send a compile-time constant response:
const routes = &.{
.get("/health", tk.Route.send(.{ .status = "ok" })),
.get("/version", tk.Route.send("1.0.0")),
};Redirects
tk.Route.redirect(url)Redirect to another URL:
const routes = &.{
.get("/old-path", tk.Route.redirect("/new-path")),
.get("/home", tk.Route.redirect("/")),
};Request Body Parsing
Routes with bodies (.post(), .put(), .patch()) automatically parse JSON into the body parameter:
fn createUser(body: User) !User {
// body is deserialized from request JSON
}Manual body reading (use body-less variants):
fn handleWebhook(req: *tk.Request) !void {
const body = try req.readAll();
}Handler Signatures
Handlers can request any injectable dependencies plus path parameters:
// No dependencies, no parameters
fn index() []const u8
// With dependencies
fn getUser(db: *Database, cache: *Cache, id: []const u8) !User
// With allocator and parameters
fn hello(arena: std.mem.Allocator, name: []const u8) ![]const u8
// With request body
fn createUser(db: *Database, body: User) !UserMiddleware
.handler(fn)Middleware receives *Context and must call ctx.next():
fn logger(ctx: *tk.Context) !void {
log.info("{s} {s}", .{@tagName(ctx.req.method), ctx.req.url});
return ctx.next();
}
const routes = &.{
.handler(logger),
.get("/", index),
};Swagger/OpenAPI
tk.swagger.json(options) Route
tk.swagger.ui(options) RouteGenerates OpenAPI specification and Swagger UI:
const routes = &.{
.get("/openapi.json", tk.swagger.json(.{ .info = .{ .title = "My API" } })),
.get("/swagger-ui", tk.swagger.ui(.{ .url = "openapi.json" })),
};