Vix.cpp v2.6.0 is here Read the blog
Skip to content

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:

txt
login state
cart state
flash messages
user preferences
temporary form state
per-browser counters
1
2
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:

txt
theme=dark
1

Session example:

txt
sid=abc123
1

The browser sends sid.

The server uses sid to load session data.

In this example, the session stores:

txt
n=1
n=2
n=3
1
2
3

The browser only receives the session id cookie.

What this example builds

The app exposes:

txt
GET /session
GET /session/value
GET /session/reset
GET /session/destroy
1
2
3
4

The behavior is:

txt
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 session
1
2
3
4
5
6
7
8
9
10
11

Use:

cpp
#include <vix/middleware.hpp>
1

The session types live in:

cpp
vix::middleware::auth
1

The main state type is:

cpp
vix::middleware::auth::Session
1

Project structure

Create:

txt
session_counter_demo/
└── session_counter.cpp
1
2

Create the file:

bash
mkdir session_counter_demo
cd session_counter_demo
touch session_counter.cpp
1
2
3

Source

Open:

txt
session_counter.cpp
1

Add:

cpp
#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;
}
1
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:

bash
vix run session_counter.cpp
1

The server listens on:

txt
http://127.0.0.1:8080
1

First request

Use -c to save the session cookie:

bash
curl -i \
  -c cookies.txt \
  http://127.0.0.1:8080/session
1
2
3

Expected body shape:

json
{
  "ok": true,
  "counter": 1,
  "session_id": "...",
  "is_new": true
}
1
2
3
4
5
6

Expected response header shape:

txt
Set-Cookie: sid=...; Path=/; HttpOnly; SameSite=Lax
1

The server created a session and stored:

txt
n=1
1

Second request

Use -b to send the saved cookie and -c to update it if needed:

bash
curl -i \
  -b cookies.txt \
  -c cookies.txt \
  http://127.0.0.1:8080/session
1
2
3
4

Expected body shape:

json
{
  "ok": true,
  "counter": 2,
  "session_id": "...",
  "is_new": false
}
1
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

bash
curl -i \
  -b cookies.txt \
  http://127.0.0.1:8080/session/value
1
2
3

Expected body shape:

json
{
  "ok": true,
  "counter": 2,
  "session_id": "...",
  "is_new": false
}
1
2
3
4
5
6

This route reads the current value without changing it.

Reset the counter

bash
curl -i \
  -b cookies.txt \
  -c cookies.txt \
  http://127.0.0.1:8080/session/reset
1
2
3
4

Expected body:

json
{
  "ok": true,
  "counter": 0,
  "message": "counter reset"
}
1
2
3
4
5

Read again:

bash
curl -i \
  -b cookies.txt \
  http://127.0.0.1:8080/session/value
1
2
3

Expected counter:

txt
0
1

Destroy the session

bash
curl -i \
  -b cookies.txt \
  -c cookies.txt \
  http://127.0.0.1:8080/session/destroy
1
2
3
4

Expected body:

json
{
  "ok": true,
  "message": "session destroyed"
}
1
2
3
4

After destroying, request /session again:

bash
curl -i \
  -b cookies.txt \
  -c cookies.txt \
  http://127.0.0.1:8080/session
1
2
3
4

Expected behavior:

txt
a new session is created
counter starts again from 1
1
2

How it works

The session middleware is installed with:

cpp
app.use("/session", middleware::app::adapt_ctx(
  middleware::auth::session(session_options)
));
1
2
3

The handler reads the session from request state:

cpp
auto &session =
  req.state<middleware::auth::Session>();
1
2

The counter is stored as a string:

cpp
session.set("n", std::to_string(n));
1

The value is read with:

cpp
auto value = session.get("n");
1

The value is removed with:

cpp
session.erase("n");
1

The full session is destroyed with:

cpp
session.destroy();
1

Session options

The example uses:

cpp
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;
1
2
3
4
5
6
7
8
9

Meaning:

txt
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 exists
1
2
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:

cpp
struct Session
{
  std::string id;
  std::unordered_map<std::string, std::string> data;

  bool is_new{false};
  bool dirty{false};
  bool destroyed{false};
};
1
2
3
4
5
6
7
8
9

Meaning:

txt
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 deleted
1
2
3
4
5
6
7
8
9
10
11
12
13
14

Most handlers only need:

cpp
session.get(...)
session.set(...)
session.erase(...)
session.destroy()
1
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:

txt
sessions disappear when the process restarts
sessions are not shared across multiple server instances
sessions are not suitable for multi-node production by default
1
2
3

For production, use a persistent or shared store.

Examples:

txt
Redis
database-backed store
distributed cache
custom ISessionStore implementation
1
2
3
4

Custom session store

The session module exposes a store interface:

cpp
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;
};
1
2
3
4
5
6
7
8
9

You can provide your own store:

cpp
session_options.store = std::make_shared<MySessionStore>();
1

Use this when session data must survive process restarts or be shared across multiple app instances.

The session middleware uses a cookie to identify the session.

In this example:

txt
sid=...
1

The cookie should usually be:

txt
HttpOnly
Secure in production
SameSite=Lax or Strict
1
2
3

The example uses local HTTP development, so:

cpp
session_options.secure = false;
1

For production HTTPS:

cpp
session_options.secure = true;
1

Session lifetime

The options include a TTL:

cpp
session_options.ttl = std::chrono::hours(24 * 7);
1

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:

cpp
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)
));
1
2
3
4
5
6
7

The order means:

txt
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 data
1
2
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:

txt
GET /login
  create CSRF token in session

POST /login
  parse form
  validate CSRF token from session
  set user id in session
1
2
3
4
5
6
7

Example session write:

cpp
session.set("user_id", "123");
1

Example session read:

cpp
auto user_id = session.get("user_id");
1

If user_id exists, the user is logged in.

Sessions with JSON APIs

Sessions can also be used for browser JSON APIs.

Example:

txt
POST /api/login
  validate credentials
  set session user_id

GET /api/me
  read session user_id
  return current user

POST /api/logout
  destroy session
1
2
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:

txt
user_id
cart_id
csrf_token
flash_message
small temporary state
1
2
3
4
5

Bad session values:

txt
large JSON payloads
full user profile copies
passwords
private keys
unbounded arrays
large cart contents
1
2
3
4
5
6

Store large or durable data in a database.

Keep the session small.

Production defaults

For production session cookies, prefer:

txt
HttpOnly = true
Secure = true
SameSite = Lax or Strict
short and intentional TTL
persistent shared store
regenerate session id after login
destroy session on logout
1
2
3
4
5
6
7

For local development:

txt
Secure = false
1

because the app usually runs on plain HTTP.

For production behind HTTPS:

txt
Secure = true
1

Common mistakes

Using sessions without cookies

A session needs a way to identify the browser.

Usually that means a cookie.

With curl, remember:

txt
-c cookies.txt
  save cookies

-b cookies.txt
  send cookies
1
2
3
4
5

This creates a new session every time:

bash
curl -i http://127.0.0.1:8080/session
curl -i http://127.0.0.1:8080/session
1
2

This keeps the same session:

bash
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/session
1
2

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:

cpp
session_options.secure = true;
1

Complete test flow

Run:

bash
vix run session_counter.cpp
1

First request:

bash
curl -i \
  -c cookies.txt \
  http://127.0.0.1:8080/session
1
2
3

Second request:

bash
curl -i \
  -b cookies.txt \
  -c cookies.txt \
  http://127.0.0.1:8080/session
1
2
3
4

Read value:

bash
curl -i \
  -b cookies.txt \
  http://127.0.0.1:8080/session/value
1
2
3

Reset:

bash
curl -i \
  -b cookies.txt \
  -c cookies.txt \
  http://127.0.0.1:8080/session/reset
1
2
3
4

Destroy:

bash
curl -i \
  -b cookies.txt \
  -c cookies.txt \
  http://127.0.0.1:8080/session/destroy
1
2
3
4

Start again:

bash
curl -i \
  -b cookies.txt \
  -c cookies.txt \
  http://127.0.0.1:8080/session
1
2
3
4

Summary

Use sessions when the server needs to remember small state per browser.

Install session middleware:

cpp
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)
));
1
2
3
4
5
6
7
8
9
10

Read the session:

cpp
auto &session =
  req.state<middleware::auth::Session>();
1
2

Set a value:

cpp
session.set("n", "1");
1

Read a value:

cpp
auto value = session.get("n");
1

Destroy the session:

cpp
session.destroy();
1

The mental model is:

txt
browser stores sid cookie
server stores session data
request sends sid
middleware loads session
handler reads or writes session
middleware saves session
1
2
3
4
5
6

Released under the MIT License.

Morty Proxy This is a proxified and sanitized view of the page, visit original site.