Basics
The basics group contains the middleware that should exist near the foundation of most Vix HTTP backends.
These middleware functions do not implement business logic. They make the request pipeline safer, easier to debug, and more predictable.
They help with:
recovering from exceptions
assigning request ids
measuring request time
rejecting oversized bodies
logging completed requestsThe basics group lives under:
namespace vix::middleware::basicsWhen using vix::App, prefer the App helpers:
namespace vix::middleware::appWhat basics provides
The basics group includes:
| Middleware | Purpose |
|---|---|
recovery() | Catch exceptions and return a normalized 500 response |
request_id() | Create or reuse a request id |
timing() | Measure request duration |
body_limit() | Reject request bodies that are too large |
logger() | Write one summary line after request handling |
For normal vix::App applications, use the App presets:
middleware::app::recovery_dev()
middleware::app::request_id_dev()
middleware::app::timing_dev()
middleware::app::body_limit_dev()
middleware::app::body_limit_write_dev(...)Basic backend setup
A small backend can start with:
#include <vix.hpp>
#include <vix/middleware.hpp>
using namespace vix;
int main()
{
App app;
app.use("/api", middleware::app::recovery_dev());
app.use("/api", middleware::app::request_id_dev());
app.use("/api", middleware::app::timing_dev());
app.use("/api", middleware::app::body_limit_write_dev(1024 * 1024));
app.get("/api/health", [](Request &, Response &res)
{
res.json({
"ok", true
});
});
app.post("/api/echo", [](Request &req, Response &res)
{
res.json({
"ok", true,
"bytes", static_cast<long long>(req.body().size())
});
});
app.run(8080);
}This gives the /api routes a basic request foundation.
recovery
catches failures
request_id
gives the request an id
timing
measures request duration
body_limit
rejects large write bodiesRecommended order
A practical order is:
app.use("/api", middleware::app::recovery_dev());
app.use("/api", middleware::app::request_id_dev());
app.use("/api", middleware::app::timing_dev());
app.use("/api", middleware::app::body_limit_write_dev(1024 * 1024));The idea is:
recovery wraps the request
request_id makes the request traceable
timing measures downstream work
body_limit rejects oversized write bodies earlyAfter basics, you can add security, authentication, parsers, cache, performance, and observability.
Example:
app.use("/api", middleware::app::recovery_dev());
app.use("/api", middleware::app::request_id_dev());
app.use("/api", middleware::app::timing_dev());
app.use("/api", middleware::app::body_limit_write_dev(1024 * 1024));
app.use("/api", middleware::app::security_headers_dev());
app.use("/api", middleware::app::cors_dev());
app.use("/api", middleware::app::rate_limit_dev());
app.use("/api/users", middleware::app::json_strict_dev(4096));Recovery
recovery() catches exceptions thrown by downstream middleware or handlers and turns them into a normalized 500 response.
Without recovery, an exception can escape the request pipeline.
With recovery, the server can respond cleanly.
app.use("/api", middleware::app::recovery_dev());
app.get("/api/fail", [](Request &, Response &)
{
throw std::runtime_error("database unavailable");
});Request:
curl -i http://127.0.0.1:8080/api/failExpected status:
500 Internal Server ErrorRecovery is usually installed early so it wraps the rest of the chain.
recovery
-> request id
-> timing
-> security
-> auth
-> parser
-> handlerConfigure recovery
Use the lower-level middleware when you need explicit options.
vix::middleware::basics::RecoveryOptions opt;
opt.include_exception_message = false;
opt.code = "internal_server_error";
opt.message = "Internal Server Error";
app.use("/api", vix::middleware::app::adapt_ctx(
vix::middleware::basics::recovery(opt)
));In development, you may include the exception message.
opt.include_exception_message = true;In production, keep it false unless you intentionally want exception details in responses.
Request id
request_id() gives each request a stable identifier.
The default header is:
x-request-idThe middleware can:
accept a valid incoming request id
generate one when missing
store it in request state
write it back to the response headerUse it when you want to connect a client request, logs, metrics, and errors.
app.use("/api", middleware::app::request_id_dev());
app.get("/api/health", [](Request &req, Response &res)
{
auto *rid = req.try_state<middleware::basics::RequestId>();
res.json({
"ok", true,
"request_id", rid ? rid->value : ""
});
});Request:
curl -i http://127.0.0.1:8080/api/healthResponse headers can include:
x-request-id: ...Response body shape:
{
"ok": true,
"request_id": "..."
}Incoming request ids
A client can send an id:
curl -i \
http://127.0.0.1:8080/api/health \
-H "x-request-id: req_demo_1234"If the id is valid, the middleware can reuse it.
Invalid ids are ignored and a new one can be generated.
Accepted ids are intentionally limited to reasonable characters and size.
This prevents unsafe or very large values from being copied into logs and response headers.
Configure request id
Use RequestIdOptions for lower-level control.
vix::middleware::basics::RequestIdOptions opt;
opt.header_name = "x-request-id";
opt.accept_incoming = true;
opt.generate_if_missing = true;
opt.always_set_response_header = true;
app.use("/api", vix::middleware::app::adapt_ctx(
vix::middleware::basics::request_id(opt)
));Main options:
| Option | Purpose |
|---|---|
header_name | Header used to read and write the request id |
accept_incoming | Accept a valid incoming request id |
generate_if_missing | Generate a request id when none is present |
always_set_response_header | Write the request id to the response |
Timing
timing() measures how long downstream middleware and handlers take to run.
It can write response headers such as:
x-response-time: 2ms
server-timing: total;dur=2It can also store the result in typed request state:
vix::middleware::basics::TimingExample:
app.use("/api", middleware::app::timing_dev());
app.get("/api/work", [](Request &, Response &res)
{
res.json({
"ok", true
});
});Request:
curl -i http://127.0.0.1:8080/api/workResponse headers can include:
x-response-time: 1ms
server-timing: total;dur=1Read Timing state
A handler can read timing state when available.
app.get("/api/debug", [](Request &req, Response &res)
{
auto *timing = req.try_state<middleware::basics::Timing>();
res.json({
"duration_ms", timing ? timing->total_ms : 0
});
});In many cases, timing is more useful as a response header or as input for logging and metrics.
Configure timing
Use TimingOptions for lower-level control.
vix::middleware::basics::TimingOptions opt;
opt.set_x_response_time = true;
opt.set_server_timing = true;
opt.store_in_state = true;
opt.x_response_time_header = "x-response-time";
opt.server_timing_header = "server-timing";
opt.server_timing_metric = "total";
app.use("/api", vix::middleware::app::adapt_ctx(
vix::middleware::basics::timing(opt)
));Main options:
| Option | Purpose |
|---|---|
set_x_response_time | Write x-response-time |
set_server_timing | Write server-timing |
store_in_state | Store Timing in request state |
x_response_time_header | Header name for response time |
server_timing_header | Header name for server timing |
server_timing_metric | Metric name inside Server-Timing |
Body limit
body_limit() rejects request bodies that are too large.
It protects the application before parsers and handlers do more work.
This is especially important before:
JSON parsing
form parsing
multipart parsing
file upload handling
authentication endpoints
public POST routesUse a body limit before parsers.
app.use("/api", middleware::app::body_limit_write_dev(1024));
app.use("/api/users", middleware::app::json_strict_dev(1024));Request with a small body:
curl -i \
-X POST http://127.0.0.1:8080/api/echo \
-H "Content-Type: text/plain" \
--data "hello"Expected status:
200 OKRequest with a large body:
python3 - <<'PY' > /tmp/large.txt
print("x" * 2048)
PY
curl -i \
-X POST http://127.0.0.1:8080/api/echo \
-H "Content-Type: text/plain" \
--data-binary @/tmp/large.txtExpected status:
413 Payload Too LargeThe handler is not called.
Body limit for write methods
Most APIs should limit write methods.
app.use("/api", middleware::app::body_limit_write_dev(1024 * 1024));This targets methods such as:
POST
PUT
PATCHIt avoids applying body rules to normal GET routes unless you explicitly want that.
Strict body limit
Some applications require Content-Length.
A strict body limit can reject requests without a length when chunked bodies are not allowed.
The lower-level option is:
vix::middleware::basics::BodyLimitOptions opt;
opt.max_bytes = 1024;
opt.apply_to_get = false;
opt.allow_chunked = false;Install it with adapt_ctx():
app.use("/api/upload", vix::middleware::app::adapt_ctx(
vix::middleware::basics::body_limit(opt)
));If the request has no Content-Length and chunked bodies are not allowed, the middleware can return:
411 Length RequiredConditional body limit
For advanced cases, BodyLimitOptions supports should_apply.
vix::middleware::basics::BodyLimitOptions opt;
opt.max_bytes = 16;
opt.should_apply = [](const vix::middleware::Context &ctx)
{
const auto &req = ctx.req();
return req.method() == "POST" ||
req.method() == "PUT" ||
req.method() == "PATCH";
};
app.use("/api", vix::middleware::app::adapt_ctx(
vix::middleware::basics::body_limit(opt)
));Use should_apply when you need custom method, route, header, or content-type rules.
For normal applications, use the App preset first.
Configure body limit
Main options:
| Option | Purpose |
|---|---|
max_bytes | Maximum allowed request body size |
apply_to_get | Whether the middleware applies to GET |
allow_chunked | Whether requests without Content-Length may pass |
should_apply | Custom predicate to decide whether the limit applies |
Common errors:
| Status | Code | Meaning |
|---|---|---|
413 | payload_too_large | Body or Content-Length exceeds the limit |
411 | length_required | Length is required but missing |
Logger
logger() writes one summary line after downstream middleware and the route handler run.
It can log:
method
path
status
duration
request id
user agent
forwarded client ipThe logger middleware uses an ILogger service from the middleware services container.
That makes it useful for custom integrations where you provide your own logging sink.
For most applications, Vix Core already has server logging. Use logger() when you want middleware-level request logging with a custom service.
Logger interface
A logger service implements:
struct ILogger
{
virtual ~ILogger() = default;
virtual void info(std::string_view msg) = 0;
virtual void warn(std::string_view msg) = 0;
virtual void error(std::string_view msg) = 0;
};The middleware can output either text or JSON-style lines depending on options.
vix::middleware::basics::LoggerOptions opt;
opt.format = vix::middleware::basics::LogFormat::Text;
opt.log_request_id = true;
opt.log_timing = true;
opt.level_from_status = true;When level_from_status is true:
status >= 500 -> error
status >= 400 -> warn
otherwise -> infoUse basics with lower-level middleware
App presets are the simplest path.
app.use("/api", middleware::app::request_id_dev());
app.use("/api", middleware::app::timing_dev());
app.use("/api", middleware::app::body_limit_write_dev(1024));Use lower-level middleware when you need exact options.
app.use("/api", vix::middleware::app::adapt_ctx(
vix::middleware::basics::request_id({
.header_name = "x-request-id",
.accept_incoming = true,
.generate_if_missing = true,
.always_set_response_header = true
})
));This pattern works for all context-based middleware:
create options
build lower-level middleware
adapt with app::adapt_ctx(...)
install with app.use(...)Complete example
#include <vix.hpp>
#include <vix/middleware.hpp>
using namespace vix;
int main()
{
App app;
app.use("/api", middleware::app::recovery_dev());
app.use("/api", middleware::app::request_id_dev());
app.use("/api", middleware::app::timing_dev());
app.use("/api", middleware::app::body_limit_write_dev(1024));
app.get("/api/health", [](Request &req, Response &res)
{
auto *rid = req.try_state<middleware::basics::RequestId>();
res.json({
"ok", true,
"request_id", rid ? rid->value : ""
});
});
app.post("/api/echo", [](Request &req, Response &res)
{
res.json({
"ok", true,
"bytes", static_cast<long long>(req.body().size())
});
});
app.get("/api/fail", [](Request &, Response &)
{
throw std::runtime_error("demo failure");
});
app.run(8080);
}Run it:
vix run basics_demo.cppTest health:
curl -i http://127.0.0.1:8080/api/healthTest body limit:
python3 - <<'PY' > /tmp/large.txt
print("x" * 2048)
PY
curl -i \
-X POST http://127.0.0.1:8080/api/echo \
--data-binary @/tmp/large.txtTest recovery:
curl -i http://127.0.0.1:8080/api/failExpected behavior:
health returns 200
large body returns 413
failure returns 500
responses include request/timing headers when enabledSummary
The basics group gives your backend its first layer of reliability.
Use it to:
catch unexpected failures
make requests traceable
measure response time
reject oversized bodies early
prepare the pipeline for security, auth, parsers, and performance middlewareA good default stack is:
app.use("/api", middleware::app::recovery_dev());
app.use("/api", middleware::app::request_id_dev());
app.use("/api", middleware::app::timing_dev());
app.use("/api", middleware::app::body_limit_write_dev(1024 * 1024));Then add the rest of your backend behavior on top.