Skip to content

HTTP Routing

Defining Routes

Routes map HTTP methods and URL paths to handler functions. They are registered on the app instance via register_endpoint.

cpp
using namespace framework;
using namespace framework::clients::http;

app.register_endpoint(
    http_verb_t::get,    // HTTP method
    "/hello",            // URL path
    handler_function     // Your handler
);

register_endpoint(method, path, handler, validator, gate, requires_auth)

ParameterTypeRequiredDescription
methodhttp_verb_tYesHTTP verb (get, post, put, delete, patch, etc.)
pathstd::stringYesURL path pattern
handlerhttp_handler_tYesFunction that processes the request and returns a response
validatorhttp_validator_tNoRuns before the handler; returns errors if input is invalid
gatehttp_gate_tNoRuns before the handler; returns false to reject (403)
requires_authboolNoIf true, JWT authentication is enforced before the handler

Route Patterns

The path string can contain dynamic segments that are extracted at match time.

Static Paths

cpp
app.register_endpoint(http_verb_t::get, "/hello", handler);
// Matches exactly: GET /hello

Dynamic (Named) Segments

Use : prefix to mark a path segment as a parameter. Parameters are captured by name and stored in http_params_.path_:

cpp
app.register_endpoint(http_verb_t::get, "/users/:id", handler);
// GET /users/42        →  params["id"] = "42"
// GET /users/abc       →  params["id"] = "abc"

app.register_endpoint(http_verb_t::get, "/posts/:post_id/comments/:comment_id", handler);
// GET /posts/5/comments/12  →  params["post_id"] = "5", params["comment_id"] = "12"

How Routing Works

The router uses a trie (prefix tree) where each node represents a path segment. When a request arrives, the router walks the trie segment by segment:

  • Static nodes match by exact string comparison.
  • Parameter nodes (prefixed with :) act as wildcards — they match any single path component and store its value by name.
  • The trie structure makes routing O(n) in path depth regardless of how many routes are registered.

Matching Priority

The trie selects the most specific match:

  1. Static segments beat parameter segments. /users/me matches the static route over /users/:id.
  2. More static segments beat fewer. A route with more matching static segments takes priority.
  3. If multiple routes have the same specificity, the one registered first wins.

If no route matches at all, the framework returns a 404 response.

cpp
// Priority example:
app.register_endpoint(http_verb_t::get, "/users/me", me_handler);       // static
app.register_endpoint(http_verb_t::get, "/users/:id", user_handler);    // parameterized

// GET /users/me  →  me_handler (static beats param)
// GET /users/42  →  user_handler (matches :id)

The Route Struct

Internally, each route is stored as a route object:

cpp
struct route {
    std::string path_;                              // URL pattern
    http_verb_t http_verb_;                         // HTTP method
    http_handler_t http_handler_;                   // Response(request) -> response
    http_validator_t http_validator_ = nullptr;      // Optional input validator
    http_gate_t http_gate = nullptr;                 // Optional authorization gate
    bool requires_auth_ = false;                     // JWT required flag
};

Handler Signature

Every handler receives a http_request and must return an http_response_t:

cpp
using http_handler_t = std::function<http_response_t(const http_request&)>;

Complete handler example

cpp
app.register_endpoint(
    http_verb_t::get,
    "/hello",
    [](const http_request& req) -> http_response_t {
        auto res = helpers::make_base_http_response(req, http_status_t::ok);
        auto body = helpers::make_base_http_payload(200, "Hello!");
        helpers::finalize_response(res, body);
        return res;
    }
);

Adding Validation

A validator runs before the handler. It returns {is_valid, errors_map}. If is_valid is false, the framework returns a 422 response.

cpp
app.register_endpoint(
    http_verb_t::post,
    "/users",
    create_user_handler,
    create_user_validator,   // runs before the handler
    nullptr,                 // no gate
    false                    // no auth required
);

See Validation for how to write validators.


Adding Authorization Gates

A gate checks authorization before the handler. The built-in gates::is_admin_all checks for admin privileges.

cpp
app.register_endpoint(
    http_verb_t::get,
    "/admin/stats",
    admin_stats_handler,
    nullptr,
    gates::is_admin_all,
    true   // requires JWT authentication
);

When requires_auth is true, a valid JWT must be present. If the gate returns false, the client receives a 403 response.


Accessing the Router Directly

cpp
auto& router = app.get_router();
router.add_route({"/path", http_verb_t::get, handler});

Useful for building dynamic route tables or defining routes outside the main app setup.


Next: Middleware.