Form Parser
This example shows how to parse application/x-www-form-urlencoded request bodies with Vix middleware.
Use this when a route receives classic HTML form data such as:
name=Gaspard&email=gaspard@example.com&message=HelloThis format is commonly used by:
HTML forms
simple contact forms
login forms
search forms
small admin forms
browser-submitted forms2
3
4
5
6
For file uploads, use multipart instead.
For JSON APIs, use the JSON parser.
What this example builds
The app exposes:
GET /
POST /contact2
The route /contact expects:
Content-Type: application/x-www-form-urlencodedand fields:
name
email
message2
3
The middleware parses the form and stores it in request state as:
middleware::parsers::FormBodyProject structure
Create:
form_parser_demo/
└── form_parser.cpp2
Create the file:
mkdir form_parser_demo
cd form_parser_demo
touch form_parser.cpp2
3
Source
Open:
form_parser.cppAdd:
#include <string>
#include <vix.hpp>
#include <vix/middleware.hpp>
using namespace vix;
static std::string form_value(
const middleware::parsers::FormBody &form,
const std::string &key)
{
auto it = form.fields.find(key);
if (it == form.fields.end())
return {};
return it->second;
}
static bool missing(const std::string &value)
{
return value.empty();
}
static void install_middleware(App &app)
{
app.use("/contact", middleware::app::request_id_dev());
app.use("/contact", middleware::app::timing_dev());
app.use("/contact", middleware::app::security_headers_dev());
app.use("/contact", middleware::app::body_limit_write_dev(
16 * 1024
));
app.use("/contact", middleware::app::form_dev(
4096
));
}
static void register_routes(App &app)
{
app.get("/", [](Request &, Response &res)
{
res.send(
"Form parser example\n"
"\n"
"Try:\n"
" curl -i -X POST http://127.0.0.1:8080/contact \\\n"
" -H \"Content-Type: application/x-www-form-urlencoded\" \\\n"
" --data \"name=Gaspard&email=gaspard@example.com&message=Hello\"\n"
);
});
app.post("/contact", [](Request &req, Response &res)
{
auto &form =
req.state<middleware::parsers::FormBody>();
const std::string name = form_value(form, "name");
const std::string email = form_value(form, "email");
const std::string message = form_value(form, "message");
if (missing(name))
{
res.status(422).json({
"ok", false,
"error", "Missing required field",
"field", "name"
});
return;
}
if (missing(email))
{
res.status(422).json({
"ok", false,
"error", "Missing required field",
"field", "email"
});
return;
}
if (missing(message))
{
res.status(422).json({
"ok", false,
"error", "Missing required field",
"field", "message"
});
return;
}
res.json({
"ok", true,
"received", true,
"name", name,
"email", email,
"message", message
});
});
}
int main()
{
App app;
install_middleware(app);
register_routes(app);
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
Run it
Run:
vix run form_parser.cppThe server listens on:
http://127.0.0.1:8080Test the home route
curl -i http://127.0.0.1:8080/Expected body:
Form parser exampleThis route is public and does not use the form parser.
Send a valid form
curl -i \
-X POST http://127.0.0.1:8080/contact \
-H "Content-Type: application/x-www-form-urlencoded" \
--data "name=Gaspard&email=gaspard@example.com&message=Hello"2
3
4
Expected status:
200 OKExpected body:
{
"ok": true,
"received": true,
"name": "Gaspard",
"email": "gaspard@example.com",
"message": "Hello"
}2
3
4
5
6
7
The form parser decodes the body and stores the fields in request state.
URL decoding
The parser handles URL-encoded values.
Example:
curl -i \
-X POST http://127.0.0.1:8080/contact \
-H "Content-Type: application/x-www-form-urlencoded" \
--data "name=Gaspard+Kirira&email=gaspard%40example.com&message=Hello+from+Vix"2
3
4
Expected decoded values:
name
Gaspard Kirira
email
gaspard@example.com
message
Hello from Vix2
3
4
5
6
7
8
In URL-encoded forms:
+
becomes a space
%40
becomes @2
3
4
5
Test missing field
Missing message:
curl -i \
-X POST http://127.0.0.1:8080/contact \
-H "Content-Type: application/x-www-form-urlencoded" \
--data "name=Gaspard&email=gaspard@example.com"2
3
4
Expected status:
422 Unprocessable EntityExpected body:
{
"ok": false,
"error": "Missing required field",
"field": "message"
}2
3
4
5
The middleware parses the HTTP body.
The handler validates application fields.
Test wrong Content-Type
Send JSON to the form route:
curl -i \
-X POST http://127.0.0.1:8080/contact \
-H "Content-Type: application/json" \
--data '{"name":"Gaspard"}'2
3
4
Expected status:
415 Unsupported Media TypeThe form parser expects:
application/x-www-form-urlencodedIf a route expects JSON, use the JSON parser instead.
Test payload too large
The example installs a broad body limit:
app.use("/contact", middleware::app::body_limit_write_dev(
16 * 1024
));2
3
and a form parser limit:
app.use("/contact", middleware::app::form_dev(
4096
));2
3
To test the form parser limit:
BIG="$(python3 -c 'print("name=Gaspard&email=gaspard@example.com&message=" + "x"*5000)')"
curl -i \
-X POST http://127.0.0.1:8080/contact \
-H "Content-Type: application/x-www-form-urlencoded" \
--data "$BIG"2
3
4
5
6
Expected status:
413 Payload Too LargeUse body limits and parser limits together:
body limit
protects the route from large requests
parser limit
protects the parser from large form bodies2
3
4
5
How it works
The key middleware is:
app.use("/contact", middleware::app::form_dev(
4096
));2
3
It checks:
Content-Type
body size
URL-encoded format2
3
Then it stores:
middleware::parsers::FormBodyThe route reads it with:
auto &form =
req.state<middleware::parsers::FormBody>();2
The fields are available in:
form.fieldswhich is a map of:
string key
string value2
Middleware order
The example installs middleware in this order:
app.use("/contact", middleware::app::request_id_dev());
app.use("/contact", middleware::app::timing_dev());
app.use("/contact", middleware::app::security_headers_dev());
app.use("/contact", middleware::app::body_limit_write_dev(
16 * 1024
));
app.use("/contact", middleware::app::form_dev(
4096
));2
3
4
5
6
7
8
9
10
11
The order matters:
request id
identify the request
timing
measure the request
security headers
harden the response
body limit
reject oversized bodies early
form parser
parse application/x-www-form-urlencoded
handler
validates fields and returns response2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
The body limit should run before the parser.
Why the parser is route-specific
The form parser is installed only on:
app.use("/contact", ...)not on:
app.use("/", ...)That matters because most routes do not receive URL-encoded forms.
Good:
app.use("/contact", middleware::app::form_dev(4096));Risky:
app.use("/", middleware::app::form_dev(4096));Install parsers only where the route expects that body format.
HTML form example
You can send the same request from a browser form.
<form method="post" action="/contact">
<label>
Name
<input name="name" />
</label>
<label>
Email
<input name="email" type="email" />
</label>
<label>
Message
<textarea name="message"></textarea>
</label>
<button type="submit">Send</button>
</form>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
By default, a normal HTML form like this sends:
application/x-www-form-urlencodedIf the form includes file inputs, use:
enctype="multipart/form-data"and use the multipart middleware instead.
Form parser vs JSON parser
Use form parser for classic HTML forms:
application/x-www-form-urlencodedUse JSON parser for API clients:
application/jsonUse multipart parser for files:
multipart/form-dataSimple rule:
HTML form without files
form parser
API request with JSON body
JSON parser
HTML form with files
multipart parser2
3
4
5
6
7
8
Add CORS for frontend apps
If a browser frontend runs on another origin, add CORS before the parser:
app.use("/contact", middleware::app::cors_dev({
"http://localhost:5173",
"http://127.0.0.1:5173"
}));
app.use("/contact", middleware::app::body_limit_write_dev(
16 * 1024
));
app.use("/contact", middleware::app::form_dev(
4096
));2
3
4
5
6
7
8
9
10
11
12
CORS should run before body parsers so preflight requests can be handled cleanly.
Production notes
For production forms, add:
CSRF protection for browser forms
rate limiting
server-side validation
email format validation
spam protection
logging
request id
clear error responses2
3
4
5
6
7
8
For forms submitted by browsers, CSRF protection is important.
Example middleware shape:
app.use("/contact", middleware::app::csrf_dev());
app.use("/contact", middleware::app::form_dev(4096));2
The exact setup depends on how your frontend obtains and sends the CSRF token.
Complete test flow
Run:
vix run form_parser.cppValid form:
curl -i \
-X POST http://127.0.0.1:8080/contact \
-H "Content-Type: application/x-www-form-urlencoded" \
--data "name=Gaspard&email=gaspard@example.com&message=Hello"2
3
4
URL-decoded form:
curl -i \
-X POST http://127.0.0.1:8080/contact \
-H "Content-Type: application/x-www-form-urlencoded" \
--data "name=Gaspard+Kirira&email=gaspard%40example.com&message=Hello+from+Vix"2
3
4
Missing field:
curl -i \
-X POST http://127.0.0.1:8080/contact \
-H "Content-Type: application/x-www-form-urlencoded" \
--data "name=Gaspard&email=gaspard@example.com"2
3
4
Wrong content type:
curl -i \
-X POST http://127.0.0.1:8080/contact \
-H "Content-Type: application/json" \
--data '{"name":"Gaspard"}'2
3
4
Large body:
BIG="$(python3 -c 'print("name=Gaspard&email=gaspard@example.com&message=" + "x"*5000)')"
curl -i \
-X POST http://127.0.0.1:8080/contact \
-H "Content-Type: application/x-www-form-urlencoded" \
--data "$BIG"2
3
4
5
6
Summary
Use the form parser when a route receives:
application/x-www-form-urlencodedThe core setup is:
app.use("/contact", middleware::app::body_limit_write_dev(
16 * 1024
));
app.use("/contact", middleware::app::form_dev(
4096
));2
3
4
5
6
7
Then read the parsed form:
auto &form =
req.state<middleware::parsers::FormBody>();2
Access fields:
auto name = form.fields["name"];The mental model is:
body_limit
protects the route
form_dev
parses URL-encoded fields
handler
validates application data2
3
4
5
6
7
8