Session Counter
This example shows how to use Vix session middleware to store server-side state per browser.
A session is useful when you need to keep small server-managed state across requests, such as:
login state
cart state
flash messages
user preferences
temporary form state
per-browser counters2
3
4
5
6
This example builds a small counter.
Each browser gets a session cookie.
The server stores the counter in the session.
Cookies vs sessions
A cookie stores data in the browser.
A session stores data on the server and usually keeps only a session id in the browser.
Cookie example:
theme=darkSession example:
sid=abc123The browser sends sid.
The server uses sid to load session data.
In this example, the session stores:
n=1
n=2
n=32
3
The browser only receives the session id cookie.
What this example builds
The app exposes:
GET /session
GET /session/value
GET /session/reset
GET /session/destroy2
3
4
The behavior is:
GET /session
increments the counter
GET /session/value
reads the current counter
GET /session/reset
sets the counter back to zero
GET /session/destroy
destroys the session2
3
4
5
6
7
8
9
10
11
Header
Use:
#include <vix/middleware.hpp>The session types live in:
vix::middleware::authThe main state type is:
vix::middleware::auth::SessionProject structure
Create:
session_counter_demo/
└── session_counter.cpp2
Create the file:
mkdir session_counter_demo
cd session_counter_demo
touch session_counter.cpp2
3
Source
Open:
session_counter.cppAdd:
#include <string>
#include <vix.hpp>
#include <vix/middleware.hpp>
using namespace vix;
static int read_counter(const middleware::auth::Session &session)
{
auto value = session.get("n");
if (!value)
return 0;
try
{
return std::stoi(*value);
}
catch (...)
{
return 0;
}
}
static void install_middleware(App &app)
{
app.use("/session", middleware::app::request_id_dev());
app.use("/session", middleware::app::timing_dev());
app.use("/session", middleware::app::security_headers_dev());
middleware::auth::SessionOptions session_options;
session_options.secret = "dev_session_secret";
session_options.cookie_name = "sid";
session_options.cookie_path = "/";
session_options.secure = false;
session_options.http_only = true;
session_options.same_site = "Lax";
session_options.auto_create = true;
app.use("/session", middleware::app::adapt_ctx(
middleware::auth::session(session_options)
));
}
static void register_routes(App &app)
{
app.get("/", [](Request &, Response &res)
{
res.send(
"Session counter example\n"
"\n"
"Try:\n"
" curl -i -c cookies.txt http://127.0.0.1:8080/session\n"
" curl -i -b cookies.txt -c cookies.txt http://127.0.0.1:8080/session\n"
" curl -i -b cookies.txt http://127.0.0.1:8080/session/value\n"
" curl -i -b cookies.txt -c cookies.txt http://127.0.0.1:8080/session/reset\n"
" curl -i -b cookies.txt -c cookies.txt http://127.0.0.1:8080/session/destroy\n"
);
});
app.get("/session", [](Request &req, Response &res)
{
auto &session =
req.state<middleware::auth::Session>();
int n = read_counter(session);
++n;
session.set("n", std::to_string(n));
res.json({
"ok", true,
"counter", n,
"session_id", session.id,
"is_new", session.is_new
});
});
app.get("/session/value", [](Request &req, Response &res)
{
auto &session =
req.state<middleware::auth::Session>();
const int n = read_counter(session);
res.json({
"ok", true,
"counter", n,
"session_id", session.id,
"is_new", session.is_new
});
});
app.get("/session/reset", [](Request &req, Response &res)
{
auto &session =
req.state<middleware::auth::Session>();
session.set("n", "0");
res.json({
"ok", true,
"counter", 0,
"message", "counter reset"
});
});
app.get("/session/destroy", [](Request &req, Response &res)
{
auto &session =
req.state<middleware::auth::Session>();
session.destroy();
res.json({
"ok", true,
"message", "session destroyed"
});
});
}
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
Run it
Run:
vix run session_counter.cppThe server listens on:
http://127.0.0.1:8080First request
Use -c to save the session cookie:
curl -i \
-c cookies.txt \
http://127.0.0.1:8080/session2
3
Expected body shape:
{
"ok": true,
"counter": 1,
"session_id": "...",
"is_new": true
}2
3
4
5
6
Expected response header shape:
Set-Cookie: sid=...; Path=/; HttpOnly; SameSite=LaxThe server created a session and stored:
n=1Second request
Use -b to send the saved cookie and -c to update it if needed:
curl -i \
-b cookies.txt \
-c cookies.txt \
http://127.0.0.1:8080/session2
3
4
Expected body shape:
{
"ok": true,
"counter": 2,
"session_id": "...",
"is_new": false
}2
3
4
5
6
The browser sent the same sid.
The server loaded the same session.
Then it incremented the counter.
Read without incrementing
curl -i \
-b cookies.txt \
http://127.0.0.1:8080/session/value2
3
Expected body shape:
{
"ok": true,
"counter": 2,
"session_id": "...",
"is_new": false
}2
3
4
5
6
This route reads the current value without changing it.
Reset the counter
curl -i \
-b cookies.txt \
-c cookies.txt \
http://127.0.0.1:8080/session/reset2
3
4
Expected body:
{
"ok": true,
"counter": 0,
"message": "counter reset"
}2
3
4
5
Read again:
curl -i \
-b cookies.txt \
http://127.0.0.1:8080/session/value2
3
Expected counter:
0Destroy the session
curl -i \
-b cookies.txt \
-c cookies.txt \
http://127.0.0.1:8080/session/destroy2
3
4
Expected body:
{
"ok": true,
"message": "session destroyed"
}2
3
4
After destroying, request /session again:
curl -i \
-b cookies.txt \
-c cookies.txt \
http://127.0.0.1:8080/session2
3
4
Expected behavior:
a new session is created
counter starts again from 12
How it works
The session middleware is installed with:
app.use("/session", middleware::app::adapt_ctx(
middleware::auth::session(session_options)
));2
3
The handler reads the session from request state:
auto &session =
req.state<middleware::auth::Session>();2
The counter is stored as a string:
session.set("n", std::to_string(n));The value is read with:
auto value = session.get("n");The value is removed with:
session.erase("n");The full session is destroyed with:
session.destroy();Session options
The example uses:
middleware::auth::SessionOptions session_options;
session_options.secret = "dev_session_secret";
session_options.cookie_name = "sid";
session_options.cookie_path = "/";
session_options.secure = false;
session_options.http_only = true;
session_options.same_site = "Lax";
session_options.auto_create = true;2
3
4
5
6
7
8
9
Meaning:
secret
secret used by the session middleware
cookie_name
browser cookie name that stores the session id
cookie_path
cookie path
secure
send cookie only over HTTPS when true
http_only
block JavaScript access to the session cookie
same_site
browser cross-site cookie policy
auto_create
create a new session when no valid session exists2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Session state fields
The session object contains:
struct Session
{
std::string id;
std::unordered_map<std::string, std::string> data;
bool is_new{false};
bool dirty{false};
bool destroyed{false};
};2
3
4
5
6
7
8
9
Meaning:
id
unique session id
data
key-value session storage
is_new
true when the session was created for this request
dirty
true when session data changed
destroyed
true when the session should be deleted2
3
4
5
6
7
8
9
10
11
12
13
14
Most handlers only need:
session.get(...)
session.set(...)
session.erase(...)
session.destroy()2
3
4
In-memory store
If no custom store is provided, the dev setup can use an in-memory store.
That is fine for examples.
But in-memory session storage is process-local.
That means:
sessions disappear when the process restarts
sessions are not shared across multiple server instances
sessions are not suitable for multi-node production by default2
3
For production, use a persistent or shared store.
Examples:
Redis
database-backed store
distributed cache
custom ISessionStore implementation2
3
4
Custom session store
The session module exposes a store interface:
class ISessionStore
{
public:
virtual ~ISessionStore() = default;
virtual std::optional<Session> load(const std::string &sid) = 0;
virtual void save(const Session &s, std::chrono::seconds ttl) = 0;
virtual void destroy(const std::string &sid) = 0;
};2
3
4
5
6
7
8
9
You can provide your own store:
session_options.store = std::make_shared<MySessionStore>();Use this when session data must survive process restarts or be shared across multiple app instances.
Cookie behavior
The session middleware uses a cookie to identify the session.
In this example:
sid=...The cookie should usually be:
HttpOnly
Secure in production
SameSite=Lax or Strict2
3
The example uses local HTTP development, so:
session_options.secure = false;For production HTTPS:
session_options.secure = true;Session lifetime
The options include a TTL:
session_options.ttl = std::chrono::hours(24 * 7);This means the session can live for seven days depending on the store behavior.
For sensitive sessions, use a shorter TTL.
For low-risk preferences, longer TTLs can be acceptable.
Middleware order
The example installs middleware in this order:
app.use("/session", middleware::app::request_id_dev());
app.use("/session", middleware::app::timing_dev());
app.use("/session", middleware::app::security_headers_dev());
app.use("/session", middleware::app::adapt_ctx(
middleware::auth::session(session_options)
));2
3
4
5
6
7
The order means:
request id
identifies the request
timing
measures the request
security headers
hardens the response
session
loads or creates session state
handler
reads and writes session data2
3
4
5
6
7
8
9
10
11
12
13
14
If the route also receives forms or JSON, install body parsers after broad safety middleware and before the handler.
Sessions with forms
Sessions are often used with forms.
Example flow:
GET /login
create CSRF token in session
POST /login
parse form
validate CSRF token from session
set user id in session2
3
4
5
6
7
Example session write:
session.set("user_id", "123");Example session read:
auto user_id = session.get("user_id");If user_id exists, the user is logged in.
Sessions with JSON APIs
Sessions can also be used for browser JSON APIs.
Example:
POST /api/login
validate credentials
set session user_id
GET /api/me
read session user_id
return current user
POST /api/logout
destroy session2
3
4
5
6
7
8
9
10
When using sessions from browser JavaScript, cookie and CORS settings matter.
For cross-origin frontend/API setups, you need to configure credentials carefully.
Security notes
Do not store large data in sessions.
Do not store secrets that do not need to be there.
Good session values:
user_id
cart_id
csrf_token
flash_message
small temporary state2
3
4
5
Bad session values:
large JSON payloads
full user profile copies
passwords
private keys
unbounded arrays
large cart contents2
3
4
5
6
Store large or durable data in a database.
Keep the session small.
Production defaults
For production session cookies, prefer:
HttpOnly = true
Secure = true
SameSite = Lax or Strict
short and intentional TTL
persistent shared store
regenerate session id after login
destroy session on logout2
3
4
5
6
7
For local development:
Secure = falsebecause the app usually runs on plain HTTP.
For production behind HTTPS:
Secure = trueCommon mistakes
Using sessions without cookies
A session needs a way to identify the browser.
Usually that means a cookie.
With curl, remember:
-c cookies.txt
save cookies
-b cookies.txt
send cookies2
3
4
5
Forgetting to save the cookie jar
This creates a new session every time:
curl -i http://127.0.0.1:8080/session
curl -i http://127.0.0.1:8080/session2
This keeps the same session:
curl -i -c cookies.txt http://127.0.0.1:8080/session
curl -i -b cookies.txt -c cookies.txt http://127.0.0.1:8080/session2
Storing too much data
Sessions should stay small.
Store IDs in the session.
Store large data in the database.
Using in-memory sessions in production
In-memory sessions disappear when the process restarts.
Use a persistent shared store for real deployments.
Forgetting Secure in production
For HTTPS production apps:
session_options.secure = true;Complete test flow
Run:
vix run session_counter.cppFirst request:
curl -i \
-c cookies.txt \
http://127.0.0.1:8080/session2
3
Second request:
curl -i \
-b cookies.txt \
-c cookies.txt \
http://127.0.0.1:8080/session2
3
4
Read value:
curl -i \
-b cookies.txt \
http://127.0.0.1:8080/session/value2
3
Reset:
curl -i \
-b cookies.txt \
-c cookies.txt \
http://127.0.0.1:8080/session/reset2
3
4
Destroy:
curl -i \
-b cookies.txt \
-c cookies.txt \
http://127.0.0.1:8080/session/destroy2
3
4
Start again:
curl -i \
-b cookies.txt \
-c cookies.txt \
http://127.0.0.1:8080/session2
3
4
Summary
Use sessions when the server needs to remember small state per browser.
Install session middleware:
middleware::auth::SessionOptions session_options;
session_options.secret = "dev_session_secret";
session_options.cookie_name = "sid";
session_options.http_only = true;
session_options.same_site = "Lax";
app.use("/session", middleware::app::adapt_ctx(
middleware::auth::session(session_options)
));2
3
4
5
6
7
8
9
10
Read the session:
auto &session =
req.state<middleware::auth::Session>();2
Set a value:
session.set("n", "1");Read a value:
auto value = session.get("n");Destroy the session:
session.destroy();The mental model is:
browser stores sid cookie
server stores session data
request sends sid
middleware loads session
handler reads or writes session
middleware saves session2
3
4
5
6