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 countersThis 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=3The browser only receives the session id cookie.
What this example builds
The app exposes:
GET /session
GET /session/value
GET /session/reset
GET /session/destroyThe 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 sessionHeader
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.cppCreate the file:
mkdir session_counter_demo
cd session_counter_demo
touch session_counter.cppSource
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;
}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/sessionExpected body shape:
{
"ok": true,
"counter": 1,
"session_id": "...",
"is_new": true
}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/sessionExpected body shape:
{
"ok": true,
"counter": 2,
"session_id": "...",
"is_new": false
}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/valueExpected body shape:
{
"ok": true,
"counter": 2,
"session_id": "...",
"is_new": false
}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/resetExpected body:
{
"ok": true,
"counter": 0,
"message": "counter reset"
}Read again:
curl -i \
-b cookies.txt \
http://127.0.0.1:8080/session/valueExpected counter:
0Destroy the session
curl -i \
-b cookies.txt \
-c cookies.txt \
http://127.0.0.1:8080/session/destroyExpected body:
{
"ok": true,
"message": "session destroyed"
}After destroying, request /session again:
curl -i \
-b cookies.txt \
-c cookies.txt \
http://127.0.0.1:8080/sessionExpected behavior:
a new session is created
counter starts again from 1How it works
The session middleware is installed with:
app.use("/session", middleware::app::adapt_ctx(
middleware::auth::session(session_options)
));The handler reads the session from request state:
auto &session =
req.state<middleware::auth::Session>();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;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 existsSession 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};
};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 deletedMost handlers only need:
session.get(...)
session.set(...)
session.erase(...)
session.destroy()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 defaultFor production, use a persistent or shared store.
Examples:
Redis
database-backed store
distributed cache
custom ISessionStore implementationCustom 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;
};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 StrictThe 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)
));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 dataIf 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 sessionExample 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 sessionWhen 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 stateBad session values:
large JSON payloads
full user profile copies
passwords
private keys
unbounded arrays
large cart contentsStore 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 logoutFor 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 cookiesForgetting 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/sessionThis 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/sessionStoring 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/sessionSecond request:
curl -i \
-b cookies.txt \
-c cookies.txt \
http://127.0.0.1:8080/sessionRead value:
curl -i \
-b cookies.txt \
http://127.0.0.1:8080/session/valueReset:
curl -i \
-b cookies.txt \
-c cookies.txt \
http://127.0.0.1:8080/session/resetDestroy:
curl -i \
-b cookies.txt \
-c cookies.txt \
http://127.0.0.1:8080/session/destroyStart again:
curl -i \
-b cookies.txt \
-c cookies.txt \
http://127.0.0.1:8080/sessionSummary
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)
));Read the session:
auto &session =
req.state<middleware::auth::Session>();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 session