BRAIDGROUP
RESEARCH & DEV
52. Framework Docs

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 routes

Under 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:

  1. Static child — exact segment match (fastest, O(1) hash lookup)
  2. Param child — :param match, stores segment value in params object
  3. Wildcard child — * match, joins remaining segments into params["*"]

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"]