Handlers
This page shows how handlers work in Vix Core.
Use it when you want to write route handlers, read requests, send responses, use route parameters, return text or JSON, and understand how Vix adapts user handlers internally.
Public header
#include <vix.hpp>You can also include the handler headers directly:
#include <vix/http/IRequestHandler.hpp>
#include <vix/http/RequestHandler.hpp>What handlers provide
A handler is the function that runs when a route matches a request.
In most Vix applications, handlers are registered through vix::App.
app.get("/", [](vix::Request &req, vix::Response &res)
{
(void)req;
res.text("Hello from Vix");
});Handlers provide access to:
- the incoming request
- route parameters
- query parameters
- headers
- request body
- JSON body
- request state
- the outgoing response
- text responses
- JSON responses
- redirects
- files
- templates
Basic handler
A basic handler receives:
vix::Request &req
vix::Response &resExample:
#include <vix.hpp>
int main()
{
vix::App app;
app.get("/", [](vix::Request &req, vix::Response &res)
{
(void)req;
res.text("Home");
});
app.run(8080);
return 0;
}Run:
vix run main.cppExpected response:
HomeHandler signature
The public handler signature is:
void(vix::Request &req, vix::Response &res)Example:
app.get("/status", [](vix::Request &req, vix::Response &res)
{
(void)req;
res.json({
{"status", "ok"}
});
});vix::Request is the incoming request object.
vix::Response is the public response helper.
Read request data
Use req to read request data.
app.get("/debug", [](vix::Request &req, vix::Response &res)
{
res.json({
{"method", req.method()},
{"path", req.path()},
{"target", req.target()}
});
});Request:
GET /debug?page=1Example response:
{
"method": "GET",
"path": "/debug",
"target": "/debug?page=1"
}Read route parameters
Use req.param(...) to read route parameters.
app.get("/users/{id}", [](vix::Request &req, vix::Response &res)
{
res.json({
{"id", req.param("id")}
});
});Request:
GET /users/42Example response:
{
"id": "42"
}Read multiple route parameters
app.get("/users/{user_id}/posts/{post_id}", [](vix::Request &req, vix::Response &res)
{
res.json({
{"user_id", req.param("user_id")},
{"post_id", req.param("post_id")}
});
});Request:
GET /users/7/posts/42Example response:
{
"user_id": "7",
"post_id": "42"
}Read query parameters
Use req.query_value(...) to read query parameters.
app.get("/search", [](vix::Request &req, vix::Response &res)
{
const std::string q = req.query_value("q", "");
const std::string page = req.query_value("page", "1");
res.json({
{"q", q},
{"page", page}
});
});Request:
GET /search?q=vix&page=2Example response:
{
"q": "vix",
"page": "2"
}Check query parameter existence
Use has_query(...).
app.get("/search", [](vix::Request &req, vix::Response &res)
{
if (!req.has_query("q"))
{
res.status(400).json({
{"error", "missing query parameter q"}
});
return;
}
res.json({
{"q", req.query_value("q")}
});
});Read headers
Use req.header(...) to read a header.
app.get("/agent", [](vix::Request &req, vix::Response &res)
{
res.json({
{"user_agent", req.header("User-Agent")}
});
});Use has_header(...) to check whether a header exists.
app.get("/auth", [](vix::Request &req, vix::Response &res)
{
if (!req.has_header("Authorization"))
{
res.status(401).json({
{"error", "missing authorization header"}
});
return;
}
res.json({{"authorized", true}});
});Read request body
Use req.body() to read the request body.
app.post("/echo", [](vix::Request &req, vix::Response &res)
{
res.text(req.body());
});Request body:
helloResponse:
helloRead JSON body
Use req.json() when the body contains JSON.
app.post("/users", [](vix::Request &req, vix::Response &res)
{
const auto &body = req.json();
res.status(201).json({
{"received", body}
});
});You can also convert JSON to a type when supported.
auto user = req.json_as<User>();Send text
Use res.text(...).
app.get("/", [](vix::Request &req, vix::Response &res)
{
(void)req;
res.text("Hello");
});Send JSON
Use res.json(...).
app.get("/api/status", [](vix::Request &req, vix::Response &res)
{
(void)req;
res.json({
{"status", "ok"},
{"server", "Vix.cpp"}
});
});Set status code
Use res.status(...).
app.post("/users", [](vix::Request &req, vix::Response &res)
{
(void)req;
res.status(201).json({
{"created", true}
});
});Send an error
app.get("/admin", [](vix::Request &req, vix::Response &res)
{
(void)req;
const bool allowed = false;
if (!allowed)
{
res.status(403).json({
{"error", "forbidden"}
});
return;
}
res.text("admin");
});Redirect
Use res.redirect(...).
app.get("/old", [](vix::Request &req, vix::Response &res)
{
(void)req;
res.redirect("/new");
});With explicit status:
app.get("/old", [](vix::Request &req, vix::Response &res)
{
(void)req;
res.redirect(301, "/new");
});Send a file
Use res.file(...).
app.get("/download", [](vix::Request &req, vix::Response &res)
{
(void)req;
res.file("public/file.txt");
});Render a template
First configure templates:
app.templates("views");Then render from a handler:
app.get("/", [](vix::Request &req, vix::Response &res)
{
(void)req;
vix::tmpl::Context ctx;
ctx.set("title", "Home");
res.render("index.html", ctx);
});Empty response
Use send() when you want to send an empty response.
app.get("/empty", [](vix::Request &req, vix::Response &res)
{
(void)req;
res.status(204).send();
});Send status
Use sendStatus(...) to set a status and send a default body when appropriate.
app.get("/missing", [](vix::Request &req, vix::Response &res)
{
(void)req;
res.sendStatus(404);
});Set headers
Use res.header(...).
app.get("/custom", [](vix::Request &req, vix::Response &res)
{
(void)req;
res.header("X-App", "Vix");
res.text("custom header");
});Use res.type(...) to set the content type.
app.get("/plain", [](vix::Request &req, vix::Response &res)
{
(void)req;
res.type("text/plain; charset=utf-8");
res.send("plain text");
});Auto-send return values
A handler can return a sendable value.
app.get("/hello", [](vix::Request &req, vix::Response &res)
{
(void)req;
(void)res;
return "Hello from Vix";
});If the response body was not already written, Vix can send the returned value automatically.
Example with JSON-like data:
app.get("/status", [](vix::Request &req, vix::Response &res)
{
(void)req;
(void)res;
return vix::json::Json{
{"status", "ok"}
};
});Return status and payload
Handlers can also return a status and payload pair when supported.
app.get("/created", [](vix::Request &req, vix::Response &res)
{
(void)req;
(void)res;
return std::pair{201, "created"};
});Or a tuple:
app.get("/accepted", [](vix::Request &req, vix::Response &res)
{
(void)req;
(void)res;
return std::tuple{202, "accepted"};
});Stop early
A handler can stop early by returning after writing a response.
app.get("/private", [](vix::Request &req, vix::Response &res)
{
if (!req.has_header("Authorization"))
{
res.status(401).json({
{"error", "unauthorized"}
});
return;
}
res.text("private content");
});Handler with middleware
Handlers often receive requests after middleware has already run.
app.use([](vix::Request &req, vix::Response &res, vix::App::Next next)
{
(void)res;
vix::print("before handler:", req.path());
next();
});
app.get("/", [](vix::Request &req, vix::Response &res)
{
(void)req;
res.text("home");
});The flow is:
middleware
-> handler
-> responseRequest state
RequestState can store request-scoped data.
This is useful for middleware sharing data with handlers.
struct CurrentUser
{
std::string id;
};
app.use([](vix::Request &req, vix::Response &res, vix::App::Next next)
{
(void)res;
req.state().set(CurrentUser{"42"});
next();
});
app.get("/me", [](vix::Request &req, vix::Response &res)
{
const auto &user = req.state().get<CurrentUser>();
res.json({
{"id", user.id}
});
});Use try_get when the value may be absent.
if (auto *user = req.state().try_get<CurrentUser>())
{
res.json({{"id", user->id}});
return;
}
res.status(401).json({{"error", "unauthorized"}});Handler errors
If a handler throws, Vix catches the exception inside the request handler adapter and sends an error response.
app.get("/fail", [](vix::Request &req, vix::Response &res)
{
(void)req;
(void)res;
throw std::runtime_error("something failed");
});In development, error responses can include more useful debugging information.
In production, handlers should return explicit error responses when possible.
Internal handler model
Public handlers are adapted into a common internal interface.
user handler
-> RequestHandler<Handler>
-> IRequestHandler
-> RouterThe internal interface is asynchronous:
virtual vix::async::core::task<void> handle_request(
const Request &req,
Response &res) = 0;This lets the router await handlers through a uniform task<void> interface.
Handler adaptation
When a route is registered through App, Vix wraps the user handler.
App::add_route
-> collect middleware
-> create final handler
-> create RequestHandler
-> router.add_routeThe wrapper is responsible for:
- running middleware
- calling the final user handler
- auto-sending return values when needed
- finalizing the response
- adapting the handler to
IRequestHandler
Route parameter extraction
Route parameters are extracted from the route pattern.
app.get("/posts/{id}", handler);For:
GET /posts/42The handler can read:
req.param("id");Internally, Vix compares the route pattern with the request path and fills the request parameter map.
Valid handler shape
The most common handler shape is:
[](vix::Request &req, vix::Response &res)
{
res.text("OK");
}This is the recommended style for application code.
Complete example
#include <vix.hpp>
struct CurrentUser
{
std::string id;
};
int main()
{
vix::App app;
app.use([](vix::Request &req, vix::Response &res, vix::App::Next next)
{
(void)res;
req.state().set(CurrentUser{"42"});
next();
});
app.get("/", [](vix::Request &req, vix::Response &res)
{
(void)req;
res.text("Home");
});
app.get("/users/{id}", [](vix::Request &req, vix::Response &res)
{
res.json({
{"id", req.param("id")}
});
});
app.post("/echo", [](vix::Request &req, vix::Response &res)
{
res.json({
{"body", req.body()}
});
});
app.get("/me", [](vix::Request &req, vix::Response &res)
{
const auto &user = req.state().get<CurrentUser>();
res.json({
{"id", user.id}
});
});
app.get("/old", [](vix::Request &req, vix::Response &res)
{
(void)req;
res.redirect("/new");
});
app.run(8080);
return 0;
}API summary
| API | Purpose |
|---|---|
req.method() | Return the HTTP method. |
req.path() | Return the path without query string. |
req.target() | Return the full request target. |
req.body() | Return the request body. |
req.param(name) | Return a route parameter. |
req.has_param(name) | Check if a route parameter exists. |
req.query() | Return parsed query parameters. |
req.query_value(name, fallback) | Return one query parameter. |
req.has_query(name) | Check if a query parameter exists. |
req.header(name) | Return a request header. |
req.has_header(name) | Check if a header exists. |
req.json() | Parse body as JSON. |
req.json_as<T>() | Convert JSON body to type T. |
req.state() | Access request-scoped state. |
res.status(code) | Set HTTP status. |
res.text(data) | Send plain text. |
res.json(value) | Send JSON. |
res.redirect(url) | Send redirect. |
res.file(path) | Send file. |
res.render(name, context) | Render a template. |
res.header(name, value) | Set a header. |
res.type(mime) | Set content type. |
res.send() | Send an empty or current response. |
res.sendStatus(code) | Send a status response. |
Best practices
Keep handlers small.
app.get("/status", [](vix::Request &req, vix::Response &res)
{
(void)req;
res.json({{"status", "ok"}});
});Use middleware for repeated logic.
app.use("/admin", auth_middleware);Use route parameters for resource identifiers.
app.get("/users/{id}", handler);Return explicit error responses.
res.status(400).json({{"error", "bad request"}});Avoid long blocking work directly inside handlers. Use the runtime/executor model for expensive work when needed.
Next steps
Read the next pages: