Middleware
This page shows how middleware works in Vix Core.
Use it when you want to run logic before route handlers, protect routes, attach middleware to prefixes, share request-scoped data, or stop a request early.
Public header
#include <vix.hpp>You can also include the app header directly:
#include <vix/app/App.hpp>What middleware provides
Middleware is code that runs before the final route handler.
It can be used for:
- logging
- authentication
- authorization
- request validation
- CORS
- rate limiting
- request state injection
- route protection
- early error responses
- shared logic across many routes
Middleware receives:
vix::Request &req
vix::Response &res
vix::App::Next nextCall next() to continue.
Do not call next() if the middleware already sent a response.
Basic middleware
#include <vix.hpp>
int main()
{
vix::App app;
app.use([](vix::Request &req, vix::Response &res, vix::App::Next next)
{
(void)res;
vix::print("request:", req.method(), req.path());
next();
});
app.get("/", [](vix::Request &req, vix::Response &res)
{
(void)req;
res.text("Home");
});
app.run(8080);
return 0;
}Run:
vix run main.cppRequest:
GET /Example response:
HomeConsole output:
request: GET /Middleware signature
The middleware signature is:
void(vix::Request &req, vix::Response &res, vix::App::Next next)Example:
app.use([](vix::Request &req, vix::Response &res, vix::App::Next next)
{
(void)req;
(void)res;
next();
});req is the incoming request.
res is the outgoing response helper.
next continues to the next middleware or final route handler.
Continue to the next middleware
Call next() when the request should continue.
app.use([](vix::Request &req, vix::Response &res, vix::App::Next next)
{
(void)req;
(void)res;
next();
});The flow is:
middleware
-> next
-> route handlerStop early
A middleware can stop the request by writing a response and not calling next().
app.use([](vix::Request &req, vix::Response &res, vix::App::Next next)
{
if (!req.has_header("Authorization"))
{
res.status(401).json({
{"error", "missing authorization header"}
});
return;
}
next();
});The flow is:
middleware
-> response
-> stopThe route handler does not run.
Global middleware
Use app.use(middleware) to register middleware for all routes.
app.use([](vix::Request &req, vix::Response &res, vix::App::Next next)
{
vix::print(req.method(), req.path());
next();
});This middleware applies to every route.
/
/api/status
/admin/dashboardPrefix middleware
Use app.use(prefix, middleware) to register middleware for a route prefix.
app.use("/admin", [](vix::Request &req, vix::Response &res, vix::App::Next next)
{
const std::string token = req.header("Authorization");
if (token.empty())
{
res.status(401).json({
{"error", "missing authorization header"}
});
return;
}
next();
});This applies to:
/admin
/admin/users
/admin/settingsIt does not apply to:
/
/api
/usersPrefix matching
A prefix middleware matches the exact prefix or a path under that prefix.
For prefix:
/adminMatched paths:
/admin
/admin/users
/admin/settingsNot matched:
/adminx
/api/adminThis prevents accidental matches with unrelated paths.
Protect a prefix
Use protect(...) when the middleware is used for route protection.
app.protect("/admin", [](vix::Request &req, vix::Response &res, vix::App::Next next)
{
const bool allowed = req.has_header("Authorization");
if (!allowed)
{
res.status(401).json({
{"error", "unauthorized"}
});
return;
}
next();
});protect(...) is a clearer alias for prefix middleware.
Protect an exact path
Use protect_exact(...) when only one path should be protected.
app.protect_exact("/admin/settings", [](vix::Request &req, vix::Response &res, vix::App::Next next)
{
(void)req;
const bool allowed = true;
if (!allowed)
{
res.status(403).text("Forbidden");
return;
}
next();
});This applies only to:
/admin/settingsIt does not apply to:
/admin
/admin/settings/profileMiddleware order
Middleware runs in registration order.
app.use([](vix::Request &req, vix::Response &res, vix::App::Next next)
{
(void)req;
(void)res;
vix::print("first");
next();
});
app.use([](vix::Request &req, vix::Response &res, vix::App::Next next)
{
(void)req;
(void)res;
vix::print("second");
next();
});For a matched route, the flow is:
first middleware
-> second middleware
-> route handlerGlobal before prefix middleware
Vix collects global middleware first, then matching prefix middleware.
app.use(global_middleware);
app.use("/admin", admin_middleware);For:
GET /admin/dashboardThe flow is:
global_middleware
-> admin_middleware
-> route handlerMiddleware and handlers
Middleware wraps the final route handler.
app.use([](vix::Request &req, vix::Response &res, vix::App::Next next)
{
vix::print("before");
next();
vix::print("after");
});
app.get("/", [](vix::Request &req, vix::Response &res)
{
(void)req;
res.text("Home");
});The flow is:
before
-> route handler
afterThis lets middleware run logic before and after the handler.
Logging middleware
#include <vix.hpp>
int main()
{
vix::App app;
app.use([](vix::Request &req, vix::Response &res, vix::App::Next next)
{
(void)res;
vix::print(req.method(), req.path());
next();
});
app.get("/", [](vix::Request &req, vix::Response &res)
{
(void)req;
res.text("Home");
});
app.run(8080);
return 0;
}Authentication middleware
#include <vix.hpp>
int main()
{
vix::App app;
app.protect("/api/private", [](vix::Request &req, vix::Response &res, vix::App::Next next)
{
const std::string token = req.header("Authorization");
if (token.empty())
{
res.status(401).json({
{"error", "missing authorization header"}
});
return;
}
next();
});
app.get("/api/private/me", [](vix::Request &req, vix::Response &res)
{
(void)req;
res.json({
{"id", "42"},
{"name", "Ada"}
});
});
app.run(8080);
return 0;
}Request without authorization:
GET /api/private/meExample response:
{
"error": "missing authorization header"
}Request validation middleware
app.use("/api", [](vix::Request &req, vix::Response &res, vix::App::Next next)
{
const std::string content_type = req.header("Content-Type");
if (req.method() == "POST" && content_type.empty())
{
res.status(400).json({
{"error", "missing content type"}
});
return;
}
next();
});CORS middleware
app.use([](vix::Request &req, vix::Response &res, vix::App::Next next)
{
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS");
res.header("Access-Control-Allow-Headers", "Content-Type, Authorization");
if (req.method() == "OPTIONS")
{
res.status(204).send();
return;
}
next();
});Request state
Middleware can attach request-scoped data to RequestState.
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();
});A handler can read the value later.
app.get("/me", [](vix::Request &req, vix::Response &res)
{
const auto &user = req.state().get<CurrentUser>();
res.json({
{"id", user.id}
});
});Optional request state
Use try_get when the value may not exist.
app.get("/me", [](vix::Request &req, vix::Response &res)
{
auto *user = req.state().try_get<CurrentUser>();
if (!user)
{
res.status(401).json({
{"error", "unauthorized"}
});
return;
}
res.json({
{"id", user->id}
});
});Middleware with route groups
Groups can register middleware for their prefix.
#include <vix.hpp>
int main()
{
vix::App app;
app.group("/admin", [](auto &admin)
{
admin.use([](vix::Request &req, vix::Response &res, vix::App::Next next)
{
const bool allowed = req.has_header("Authorization");
if (!allowed)
{
res.status(401).json({
{"error", "unauthorized"}
});
return;
}
next();
});
admin.get("/dashboard", [](vix::Request &req, vix::Response &res)
{
(void)req;
res.text("Admin dashboard");
});
});
app.run(8080);
return 0;
}The middleware applies to:
/admin/dashboardNested group middleware
app.group("/api", [](auto &api)
{
api.use([](vix::Request &req, vix::Response &res, vix::App::Next next)
{
res.header("X-API", "Vix");
next();
});
api.group("/v1", [](auto &v1)
{
v1.get("/status", [](vix::Request &req, vix::Response &res)
{
(void)req;
res.json({
{"status", "ok"}
});
});
});
});The route becomes:
GET /api/v1/statusThe middleware is registered for:
/apiSo it applies to the nested route.
Stop before the handler
Middleware can prevent the final route handler from running.
app.use("/locked", [](vix::Request &req, vix::Response &res, vix::App::Next next)
{
(void)req;
(void)next;
res.status(423).json({
{"error", "locked"}
});
});
app.get("/locked/page", [](vix::Request &req, vix::Response &res)
{
(void)req;
res.text("this handler will not run");
});Continue conditionally
app.use("/api", [](vix::Request &req, vix::Response &res, vix::App::Next next)
{
if (req.header("X-Disabled") == "1")
{
res.status(403).json({
{"error", "disabled"}
});
return;
}
next();
});Middleware and response headers
Middleware can add headers to every response.
app.use([](vix::Request &req, vix::Response &res, vix::App::Next next)
{
(void)req;
res.header("X-Powered-By", "Vix.cpp");
next();
});Middleware and timing
Middleware can measure request duration.
#include <chrono>
app.use([](vix::Request &req, vix::Response &res, vix::App::Next next)
{
(void)req;
const auto start = std::chrono::steady_clock::now();
next();
const auto end = std::chrono::steady_clock::now();
const auto ms =
std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
res.header("X-Response-Time-Ms", std::to_string(ms));
});Middleware chain model
The middleware chain is recursive.
middleware[0]
-> next
-> middleware[1]
-> next
-> middleware[2]
-> next
-> final handlerA middleware decides whether the chain continues.
call next
-> continue
do not call next
-> stopWhen middleware is collected
When a route is registered, Vix collects middleware that applies to the route path.
App::add_route
-> collect matching middleware
-> wrap final handler
-> register handler in RouterThis means middleware should normally be registered before the routes that need it.
Recommended:
app.use("/admin", admin_middleware);
app.get("/admin/dashboard", dashboard_handler);Avoid:
app.get("/admin/dashboard", dashboard_handler);
app.use("/admin", admin_middleware);Middleware with static files
Static files are served through the application not-found fallback.
Middleware registered for a prefix can still be useful when static files are mounted under that prefix.
app.use("/assets", [](vix::Request &req, vix::Response &res, vix::App::Next next)
{
res.header("X-Static", "true");
next();
});
app.static_dir("public/assets", "/assets");Complete example
#include <vix.hpp>
#include <chrono>
#include <string>
struct CurrentUser
{
std::string id;
};
int main()
{
vix::App app;
app.use([](vix::Request &req, vix::Response &res, vix::App::Next next)
{
const auto start = std::chrono::steady_clock::now();
next();
const auto end = std::chrono::steady_clock::now();
const auto ms =
std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
res.header("X-Response-Time-Ms", std::to_string(ms));
vix::print(req.method(), req.path(), ms);
});
app.protect("/api/private", [](vix::Request &req, vix::Response &res, vix::App::Next next)
{
const std::string token = req.header("Authorization");
if (token.empty())
{
res.status(401).json({
{"error", "missing authorization header"}
});
return;
}
req.state().set(CurrentUser{"42"});
next();
});
app.get("/", [](vix::Request &req, vix::Response &res)
{
(void)req;
res.text("Home");
});
app.get("/api/private/me", [](vix::Request &req, vix::Response &res)
{
const auto &user = req.state().get<CurrentUser>();
res.json({
{"id", user.id}
});
});
app.run(8080);
return 0;
}API summary
| API | Purpose |
|---|---|
app.use(middleware) | Register global middleware. |
app.use(prefix, middleware) | Register prefix middleware. |
app.protect(prefix, middleware) | Protect a route prefix. |
app.protect_exact(path, middleware) | Protect one exact path. |
Group::use(middleware) | Register middleware for a group prefix. |
Group::protect(prefix, middleware) | Protect a group sub-prefix. |
Group::protect_exact(path, middleware) | Protect one exact group path. |
vix::App::Next | Continue to the next middleware or final handler. |
req.state().set<T>(value) | Store request-scoped data. |
req.state().get<T>() | Read required request-scoped data. |
req.state().try_get<T>() | Read optional request-scoped data. |
Best practices
Register middleware before the routes that need it.
app.use("/admin", auth_middleware);
app.get("/admin/dashboard", dashboard_handler);Keep middleware focused on one responsibility.
logging_middleware
auth_middleware
cors_middlewareAlways call next() only when the request should continue.
if (!allowed)
{
res.status(403).json({{"error", "forbidden"}});
return;
}
next();Use request state to pass data from middleware to handlers.
req.state().set(CurrentUser{"42"});Use prefix middleware for sections of your app.
app.use("/api", api_middleware);
app.use("/admin", admin_middleware);Next steps
Read the next pages: