Routing
Junction uses a trie-based router for O(k) path matching (where k is the number of path segments). Routes are registered with junction.route() and matched via router.match_route() internally. The router supports static segments, path parameters, wildcard segments, named routes, route groups, host-based routing, and subdomain routing.
Basic Route Registration
Use the route(app, method, path, handler) function to register individual handlers. The method is a string — "GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", or "HEAD". The handler receives a Request and returns a Response.
junction.route(app, "GET", "/health", fn(req: Request) -> Response {
return junction.json({"status": "ok"}, 200);
});
junction.route(app, "POST", "/api/events", fn(req: Request) -> Response {
let data = junction.parse_json(req.body);
return junction.json({"id": "evt_123"}, 201);
});Path Parameters
Segments prefixed with : are treated as named path parameters. They are extracted into req.params during matching.
junction.route(app, "GET", "/users/:user_id", fn(req: Request) -> Response {
let uid = req.params["user_id"];
return junction.json({"user_id": uid}, 200);
});
junction.route(app, "GET", "/orgs/:org_id/repos/:repo_id/issues", fn(req: Request) -> Response {
let org = req.params["org_id"];
let repo = req.params["repo_id"];
return junction.json({"org": org, "repo": repo}, 200);
});Wildcard Routes
The * segment matches any remaining path segments. The captured value is stored in req.params["*"].
junction.route(app, "GET", "/files/*", fn(req: Request) -> Response {
let filepath = req.params["*"];
return junction.file_response("public/" + filepath, "application/octet-stream");
});Named Routes
Named routes allow reverse URL generation via url_for(). Register with named_route(app, name, method, path, handler), then generate paths with url_for(app, name, {"param": value}).
junction.named_route(app, "user_profile", "GET", "/users/:user_id", fn(req: Request) -> Response {
return junction.json({"profile": true}, 200);
});
fn build_profile_url(app: JunctionApp, user_id: string) -> string {
return junction.url_for(app, "user_profile", {"user_id": user_id});
}
// Returns "/users/abc123"Route Groups
Groups share a common URL prefix and can apply middleware to all routes in the group. Use route_group(app, prefix, group_fn) and route_in_group(app, group, method, path, handler).
junction.route_group(app, "/api/v1", fn(app: JunctionApp, group: object) {
junction.group_middleware(group, fn(req: Request) -> Response {
if (!junction.require_auth(req)) {
return junction.error("UNAUTHORIZED", "Auth required", 401);
}
return null;
});
junction.route_in_group(app, group, "GET", "/users", fn(req: Request) -> Response {
return junction.json({"users": []}, 200);
});
junction.route_in_group(app, group, "POST", "/events", fn(req: Request) -> Response {
return junction.json({"created": true}, 201);
});
});Host and Subdomain Routing
Routes can be scoped to specific hostnames or subdomains using host_route() and subdomain_route().
junction.host_route(app, "api.example.com", "GET", "/status", fn(req: Request) -> Response {
return junction.json({"host": "api"}, 200);
});
junction.subdomain_route(app, "admin", "GET", "/dashboard", fn(req: Request) -> Response {
return junction.text("Admin Dashboard", 200);
});Method-Based Dispatch
Multiple HTTP methods on the same path are independent routes. The router returns 405 Method Not Allowed with an Allow header when a path matches but the method does not.
junction.route(app, "GET", "/resource", fn(req: Request) -> Response {
return junction.json({"action": "list"}, 200);
});
junction.route(app, "POST", "/resource", fn(req: Request) -> Response {
return junction.json({"action": "create"}, 201);
});
junction.route(app, "PUT", "/resource", fn(req: Request) -> Response {
return junction.json({"action": "replace"}, 200);
});
junction.route(app, "DELETE", "/resource", fn(req: Request) -> Response {
return junction.no_content();
});Route Listing
Use the CLI to list all registered routes:
braidc junction routesUnder the hood, router.get_allowed_methods(router, path) returns the set of HTTP methods registered for a given path.
Trie-Based Routing Algorithm
The router tokenizes each path by splitting on /, then walks a trie node-by-node. For each segment, the matcher tries in order:
- Static child — exact segment match (fastest, O(1) hash lookup)
- Param child —
:parammatch, stores segment value in params object - Wildcard child —
*match, joins remaining segments intoparams["*"]
If no match is found at any node, the router returns null (404). If the path matches but the HTTP method is not registered, the router returns the allowed methods for a 405 response.
Low-Level Router API
The router module can be used independently:
let router = router.new_router();
router.add_route(router, "GET", "/users/:id", my_handler);
let match = router.match_route(router, "GET", "/users/42");
// match = {"handler": my_handler, "params": {"id": "42"}}
let exists = router.route_exists(router, "GET", "/users/42");
// true
let methods = router.get_allowed_methods(router, "/users/:id");
// ["GET"]