Quick Start
This page shows the fastest useful way to start with vix::middleware.
The goal is to build a small API that already behaves like a real backend:
security headers
CORS
rate limiting
body size limits
strict JSON parsing
typed request state
route-level protection2
3
4
5
6
7
The example uses vix::App, because that is the normal application model for Vix backends.
Create a small API
Create a file:
middleware_quick_start.cppAdd this code:
#include <vix.hpp>
#include <vix/middleware.hpp>
using namespace vix;
int main()
{
App app;
app.use("/api", middleware::app::security_headers_dev());
app.use("/api", middleware::app::cors_dev({"https://example.com"}));
app.use("/api", middleware::app::rate_limit_dev(60));
app.use("/api", middleware::app::body_limit_write_dev(1024));
app.use("/api/users", middleware::app::json_strict_dev(1024));
app.get("/api/health", [](Request &, Response &res)
{
res.json({
"ok", true,
"service", "vix"
});
});
app.post("/api/users", [](Request &req, Response &res)
{
auto &body = req.state<middleware::parsers::JsonBody>();
const std::string name = body.value.value("name", "");
const std::string email = body.value.value("email", "");
if (name.empty())
{
res.status(422).json({
"ok", false,
"error", "Missing name"
});
return;
}
if (email.empty())
{
res.status(422).json({
"ok", false,
"error", "Missing email"
});
return;
}
res.status(201).json({
"ok", true,
"user", {
"name", name,
"email", email
}
});
});
app.run(8080);
return 0;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
Run it:
vix run middleware_quick_start.cppThe server listens on:
http://127.0.0.1:8080Test the health route
curl -i http://127.0.0.1:8080/api/healthExpected shape:
HTTP/1.1 200 OKBody shape:
{
"ok": true,
"service": "vix"
}2
3
4
This route does not need a request body. It still receives the middleware installed on /api, such as security headers, CORS, and rate limiting.
Send valid JSON
curl -i \
-X POST http://127.0.0.1:8080/api/users \
-H "Content-Type: application/json" \
-d '{"name":"Ada","email":"ada@example.com"}'2
3
4
Expected status:
201 CreatedExpected body shape:
{
"ok": true,
"user": {
"name": "Ada",
"email": "ada@example.com"
}
}2
3
4
5
6
7
The handler does not parse raw JSON manually.
The JSON parser middleware already parsed the body and stored it in typed request state:
auto &body = req.state<vix::middleware::parsers::JsonBody>();That is one of the main middleware patterns in Vix.
A middleware does the reusable work once. The handler reads the result.
Send invalid JSON
curl -i \
-X POST http://127.0.0.1:8080/api/users \
-H "Content-Type: application/json" \
-d '{"name":}'2
3
4
Expected status:
400 Bad RequestThe route handler is not called.
The JSON middleware stops the request before the handler because the body is invalid.
Send the wrong content type
curl -i \
-X POST http://127.0.0.1:8080/api/users \
-H "Content-Type: text/plain" \
-d '{"name":"Ada"}'2
3
4
Expected status:
415 Unsupported Media Typejson_strict_dev() requires a JSON content type.
That means the handler can assume that a successful request has already passed the JSON parser.
Send an empty body
curl -i \
-X POST http://127.0.0.1:8080/api/users \
-H "Content-Type: application/json" \
-d ''2
3
4
Expected status:
400 Bad Requestjson_strict_dev() rejects an empty body.
Use strict parsing for routes where a body is required.
Use the more relaxed JSON parser when an empty body is acceptable.
Send a body that is too large
The example limits write request bodies to 1024 bytes:
app.use("/api", middleware::app::body_limit_write_dev(1024));Try a larger body:
python3 - <<'PY' > /tmp/large.json
print('{"name":"' + 'a' * 2000 + '","email":"ada@example.com"}')
PY
curl -i \
-X POST http://127.0.0.1:8080/api/users \
-H "Content-Type: application/json" \
--data-binary @/tmp/large.json2
3
4
5
6
7
8
Expected status:
413 Payload Too LargeThe body limit middleware rejects the request before JSON parsing.
This order matters.
body limit
-> JSON parser
-> handler2
3
Rejecting oversized requests early avoids unnecessary parsing work.
What happened in the request flow
For POST /api/users, the request flow is:
request
-> security headers middleware
-> CORS middleware
-> rate limit middleware
-> body limit middleware
-> strict JSON parser
-> route handler
-> response2
3
4
5
6
7
8
Some middleware works before the handler.
CORS
rate limit
body limit
JSON parser2
3
4
Some middleware can work after the handler.
security headers
timing
compression
ETag
logging
metrics2
3
4
5
6
A middleware continues the request by calling next().
A middleware stops the request by sending a response and not calling next().
That is the core middleware model.
Add API key protection
Now protect an admin route.
Add this middleware before the route:
app.use("/api/admin", middleware::app::api_key_dev("dev_key_123"));Add the route:
app.get("/api/admin/status", [](Request &req, Response &res)
{
auto &key = req.state<middleware::auth::ApiKey>();
res.json({
"ok", true,
"admin", true,
"key_size", static_cast<long long>(key.value.size())
});
});2
3
4
5
6
7
8
9
10
Full protected section:
app.use("/api/admin", middleware::app::api_key_dev("dev_key_123"));
app.get("/api/admin/status", [](Request &req, Response &res)
{
auto &key = req.state<middleware::auth::ApiKey>();
res.json({
"ok", true,
"admin", true,
"key_size", static_cast<long long>(key.value.size())
});
});2
3
4
5
6
7
8
9
10
11
12
Test without a key:
curl -i http://127.0.0.1:8080/api/admin/statusExpected status:
401 UnauthorizedTest with the wrong key:
curl -i \
http://127.0.0.1:8080/api/admin/status \
-H "x-api-key: wrong"2
3
Expected status:
403 ForbiddenTest with the valid key:
curl -i \
http://127.0.0.1:8080/api/admin/status \
-H "x-api-key: dev_key_123"2
3
Expected status:
200 OKWhen the key is valid, the middleware stores:
vix::middleware::auth::ApiKeyThe route can read it from request state.
Do not return real API keys in production responses. The example only shows how typed state works.
Use prefixes intentionally
Middleware installed on a prefix applies to matching routes.
app.use("/api", middleware::app::rate_limit_dev());This applies to:
/api
/api/users
/api/admin/status2
3
Middleware installed on a more specific prefix applies only there.
app.use("/api/users", middleware::app::json_strict_dev(1024));This applies to:
/api/users
/api/users/1232
It does not apply to:
/api/health
/api/admin/status2
Use broad middleware for general backend behavior.
Use narrow middleware for route-specific behavior.
Recommended order
A practical order for many APIs is:
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", middleware::app::body_limit_write_dev(1024 * 1024));
app.use("/api/private", middleware::app::api_key_dev("secret"));
app.use("/api/json", middleware::app::json_strict_dev(1024 * 1024));2
3
4
5
6
7
The idea is:
security headers
add safe browser response headers
CORS
handle browser cross-origin rules
rate limit
reject abusive clients early
body limit
reject large bodies before parsing
authentication
reject unauthorized callers
parser
parse the body before the handler
handler
run application logic2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
The exact order can change by application, but this shape is a good starting point.
App presets vs low-level middleware
For normal vix::App applications, prefer App presets:
middleware::app::cors_dev()
middleware::app::rate_limit_dev()
middleware::app::json_strict_dev()
middleware::app::api_key_dev("secret")2
3
4
These return vix::App::Middleware, so they work directly with:
app.use(...)The lower-level middleware lives in namespaces such as:
vix::middleware::security
vix::middleware::auth
vix::middleware::parsers
vix::middleware::performance2
3
4
When you need lower-level control, adapt it:
auto mw = vix::middleware::app::adapt_ctx(
vix::middleware::parsers::json({
.require_content_type = true,
.allow_empty = false,
.max_bytes = 4096,
.store_in_state = true
})
);
app.use("/api/custom-json", std::move(mw));2
3
4
5
6
7
8
9
10
Use the App presets first.
Use lower-level middleware when the preset is not specific enough.
Version note
For Vix.cpp v2.6.2 and newer, this is enough:
#include <vix.hpp>
#include <vix/middleware.hpp>2
For Vix.cpp v2.6.0 and v2.6.1, App integration headers may need to be included explicitly:
#include <vix.hpp>
#include <vix/middleware.hpp>
#include <vix/middleware/app/adapter.hpp>
#include <vix/middleware/app/app_middleware.hpp>
#include <vix/middleware/app/http_cache.hpp>
#include <vix/middleware/app/presets.hpp>2
3
4
5
6
7
Use the shorter form when your project is on v2.6.2 or newer.
Static files are separate
Static file serving is handled by vix::App, not by the middleware module.
Use:
app.static_dir("public", "/", "index.html");for public files.
Middleware can still enhance static responses through a static response hook, for example compression, but static file routing itself belongs to Core.
Keep this mental model:
Core serves static files.
Middleware protects and enhances HTTP behavior.2
Next steps
Continue with:
App Integration
Core Concepts
Basics
Security
Authentication
Parsers
HTTP Cache
Performance
Observability
API Reference2
3
4
5
6
7
8
9
10
The quick start shows the normal path.
The next pages explain how the pieces work and how to configure them.