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

Middleware ​

This page shows how middleware works in Vix Core.

Use it when you want to run logic before route handlers, protect routes, attach middleware to prefixes, share request-scoped data, or stop a request early.

Public header ​

cpp
#include <vix.hpp>

You can also include the app header directly:

cpp
#include <vix/app/App.hpp>

What middleware provides ​

Middleware is code that runs before the final route handler.

It can be used for:

  • logging
  • authentication
  • authorization
  • request validation
  • CORS
  • rate limiting
  • request state injection
  • route protection
  • early error responses
  • shared logic across many routes

Middleware receives:

cpp
vix::Request &req
vix::Response &res
vix::App::Next next

Call next() to continue.

Do not call next() if the middleware already sent a response.

Basic middleware ​

cpp
#include <vix.hpp>

int main()
{
  vix::App app;

  app.use([](vix::Request &req, vix::Response &res, vix::App::Next next)
  {
    (void)res;

    vix::print("request:", req.method(), req.path());

    next();
  });

  app.get("/", [](vix::Request &req, vix::Response &res)
  {
    (void)req;

    res.text("Home");
  });

  app.run(8080);

  return 0;
}

Run:

bash
vix run main.cpp

Request:

text
GET /

Example response:

text
Home

Console output:

text
request: GET /

Middleware signature ​

The middleware signature is:

cpp
void(vix::Request &req, vix::Response &res, vix::App::Next next)

Example:

cpp
app.use([](vix::Request &req, vix::Response &res, vix::App::Next next)
{
  (void)req;
  (void)res;

  next();
});

req is the incoming request.

res is the outgoing response helper.

next continues to the next middleware or final route handler.

Continue to the next middleware ​

Call next() when the request should continue.

cpp
app.use([](vix::Request &req, vix::Response &res, vix::App::Next next)
{
  (void)req;
  (void)res;

  next();
});

The flow is:

text
middleware
  -> next
  -> route handler

Stop early ​

A middleware can stop the request by writing a response and not calling next().

cpp
app.use([](vix::Request &req, vix::Response &res, vix::App::Next next)
{
  if (!req.has_header("Authorization"))
  {
    res.status(401).json({
      "error", "missing authorization header"
    });

    return;
  }

  next();
});

The flow is:

text
middleware
  -> response
  -> stop

The route handler does not run.

Global middleware ​

Use app.use(middleware) to register middleware for all routes.

cpp
app.use([](vix::Request &req, vix::Response &res, vix::App::Next next)
{
  vix::print(req.method(), req.path());

  next();
});

This middleware applies to every route.

text
/
 /api/status
 /admin/dashboard

Prefix middleware ​

Use app.use(prefix, middleware) to register middleware for a route prefix.

cpp
app.use("/admin", [](vix::Request &req, vix::Response &res, vix::App::Next next)
{
  const std::string token = req.header("Authorization");

  if (token.empty())
  {
    res.status(401).json({
      "error", "missing authorization header"
    });
    return;
  }

  next();
});

This applies to:

text
/admin
/admin/users
/admin/settings

It does not apply to:

text
/
 /api
 /users

Prefix matching ​

A prefix middleware matches the exact prefix or a path under that prefix.

For prefix:

text
/admin

Matched paths:

text
/admin
/admin/users
/admin/settings

Not matched:

text
/adminx
/api/admin

This prevents accidental matches with unrelated paths.

Protect a prefix ​

Use protect(...) when the middleware is used for route protection.

cpp
app.protect("/admin", [](vix::Request &req, vix::Response &res, vix::App::Next next)
{
  const bool allowed = req.has_header("Authorization");

  if (!allowed)
  {
    res.status(401).json({
      "error", "unauthorized"
    });
    return;
  }

  next();
});

protect(...) is a clearer alias for prefix middleware.

Protect an exact path ​

Use protect_exact(...) when only one path should be protected.

cpp
app.protect_exact("/admin/settings", [](vix::Request &req, vix::Response &res, vix::App::Next next)
{
  (void)req;

  const bool allowed = true;

  if (!allowed)
  {
    res.status(403).text("Forbidden");
    return;
  }

  next();
});

This applies only to:

text
/admin/settings

It does not apply to:

text
/admin
/admin/settings/profile

Middleware order ​

Middleware runs in registration order.

cpp
app.use([](vix::Request &req, vix::Response &res, vix::App::Next next)
{
  (void)req;
  (void)res;

  vix::print("first");

  next();
});

app.use([](vix::Request &req, vix::Response &res, vix::App::Next next)
{
  (void)req;
  (void)res;

  vix::print("second");

  next();
});

For a matched route, the flow is:

text
first middleware
  -> second middleware
  -> route handler

Global before prefix middleware ​

Vix collects global middleware first, then matching prefix middleware.

cpp
app.use(global_middleware);
app.use("/admin", admin_middleware);

For:

text
GET /admin/dashboard

The flow is:

text
global_middleware
  -> admin_middleware
  -> route handler

Middleware and handlers ​

Middleware wraps the final route handler.

cpp
app.use([](vix::Request &req, vix::Response &res, vix::App::Next next)
{
  vix::print("before");

  next();

  vix::print("after");
});

app.get("/", [](vix::Request &req, vix::Response &res)
{
  (void)req;

  res.text("Home");
});

The flow is:

text
before
  -> route handler
after

This lets middleware run logic before and after the handler.

Logging middleware ​

cpp
#include <vix.hpp>

int main()
{
  vix::App app;

  app.use([](vix::Request &req, vix::Response &res, vix::App::Next next)
  {
    (void)res;

    vix::print(req.method(), req.path());

    next();
  });

  app.get("/", [](vix::Request &req, vix::Response &res)
  {
    (void)req;

    res.text("Home");
  });

  app.run(8080);

  return 0;
}

Authentication middleware ​

cpp
#include <vix.hpp>

int main()
{
  vix::App app;

  app.protect("/api/private", [](vix::Request &req, vix::Response &res, vix::App::Next next)
  {
    const std::string token = req.header("Authorization");

    if (token.empty())
    {
      res.status(401).json({
        "error", "missing authorization header"
      });
      return;
    }

    next();
  });

  app.get("/api/private/me", [](vix::Request &req, vix::Response &res)
  {
    (void)req;

    res.json({
      "id", "42",
      "name", "Ada"
    });
  });

  app.run(8080);

  return 0;
}

Request without authorization:

text
GET /api/private/me

Example response:

json
{
  "error": "missing authorization header"
}

Request validation middleware ​

cpp
app.use("/api", [](vix::Request &req, vix::Response &res, vix::App::Next next)
{
  const std::string content_type = req.header("Content-Type");

  if (req.method() == "POST" && content_type.empty())
  {
    res.status(400).json({
      "error", "missing content type"
    });
    return;
  }

  next();
});

CORS middleware ​

cpp
app.use([](vix::Request &req, vix::Response &res, vix::App::Next next)
{
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS");
  res.header("Access-Control-Allow-Headers", "Content-Type, Authorization");

  if (req.method() == "OPTIONS")
  {
    res.status(204).send();
    return;
  }

  next();
});

Request state ​

Middleware can attach request-scoped data to RequestState.

cpp
struct CurrentUser
{
  std::string id;
};

app.use([](vix::Request &req, vix::Response &res, vix::App::Next next)
{
  (void)res;

  req.state().set(CurrentUser{"42"});

  next();
});

A handler can read the value later.

cpp
app.get("/me", [](vix::Request &req, vix::Response &res)
{
  const auto &user = req.state().get<CurrentUser>();

  res.json({
    "id", user.id
  });
});

Optional request state ​

Use try_get when the value may not exist.

cpp
app.get("/me", [](vix::Request &req, vix::Response &res)
{
  auto *user = req.state().try_get<CurrentUser>();

  if (!user)
  {
    res.status(401).json({
      "error", "unauthorized"
    });
    return;
  }

  res.json({
    "id", user->id
  });
});

Middleware with route groups ​

Groups can register middleware for their prefix.

cpp
#include <vix.hpp>

int main()
{
  vix::App app;

  app.group("/admin", [](auto &admin)
  {
    admin.use([](vix::Request &req, vix::Response &res, vix::App::Next next)
    {
      const bool allowed = req.has_header("Authorization");

      if (!allowed)
      {
        res.status(401).json({
          "error", "unauthorized"
        });
        return;
      }

      next();
    });

    admin.get("/dashboard", [](vix::Request &req, vix::Response &res)
    {
      (void)req;

      res.text("Admin dashboard");
    });
  });

  app.run(8080);

  return 0;
}

The middleware applies to:

text
/admin/dashboard

Nested group middleware ​

cpp
app.group("/api", [](auto &api)
{
  api.use([](vix::Request &req, vix::Response &res, vix::App::Next next)
  {
    res.header("X-API", "Vix");
    next();
  });

  api.group("/v1", [](auto &v1)
  {
    v1.get("/status", [](vix::Request &req, vix::Response &res)
    {
      (void)req;

      res.json({
        "status", "ok"
      });
    });
  });
});

The route becomes:

text
GET /api/v1/status

The middleware is registered for:

text
/api

So it applies to the nested route.

Stop before the handler ​

Middleware can prevent the final route handler from running.

cpp
app.use("/locked", [](vix::Request &req, vix::Response &res, vix::App::Next next)
{
  (void)req;
  (void)next;

  res.status(423).json({
    "error", "locked"
  });
});

app.get("/locked/page", [](vix::Request &req, vix::Response &res)
{
  (void)req;

  res.text("this handler will not run");
});

Continue conditionally ​

cpp
app.use("/api", [](vix::Request &req, vix::Response &res, vix::App::Next next)
{
  if (req.header("X-Disabled") == "1")
  {
    res.status(403).json({
      "error", "disabled"
    });
    return;
  }

  next();
});

Middleware and response headers ​

Middleware can add headers to every response.

cpp
app.use([](vix::Request &req, vix::Response &res, vix::App::Next next)
{
  (void)req;

  res.header("X-Powered-By", "Vix.cpp");

  next();
});

Middleware and timing ​

Middleware can measure request duration.

cpp
#include <chrono>

app.use([](vix::Request &req, vix::Response &res, vix::App::Next next)
{
  (void)req;

  const auto start = std::chrono::steady_clock::now();

  next();

  const auto end = std::chrono::steady_clock::now();
  const auto ms =
      std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();

  res.header("X-Response-Time-Ms", std::to_string(ms));
});

Middleware chain model ​

The middleware chain is recursive.

text
middleware[0]
  -> next
    -> middleware[1]
      -> next
        -> middleware[2]
          -> next
            -> final handler

A middleware decides whether the chain continues.

text
call next
  -> continue

do not call next
  -> stop

When middleware is collected ​

When a route is registered, Vix collects middleware that applies to the route path.

text
App::add_route
  -> collect matching middleware
  -> wrap final handler
  -> register handler in Router

This means middleware should normally be registered before the routes that need it.

Recommended:

cpp
app.use("/admin", admin_middleware);
app.get("/admin/dashboard", dashboard_handler);

Avoid:

cpp
app.get("/admin/dashboard", dashboard_handler);
app.use("/admin", admin_middleware);

Middleware with static files ​

Static files are served through the application not-found fallback.

Middleware registered for a prefix can still be useful when static files are mounted under that prefix.

cpp
app.use("/assets", [](vix::Request &req, vix::Response &res, vix::App::Next next)
{
  res.header("X-Static", "true");

  next();
});

app.static_dir("public/assets", "/assets");

Complete example ​

cpp
#include <vix.hpp>

#include <chrono>
#include <string>

struct CurrentUser
{
  std::string id;
};

int main()
{
  vix::App app;

  app.use([](vix::Request &req, vix::Response &res, vix::App::Next next)
  {
    const auto start = std::chrono::steady_clock::now();

    next();

    const auto end = std::chrono::steady_clock::now();
    const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();

    res.header("X-Response-Time-Ms", std::to_string(ms));

    vix::print(req.method(), req.path(), ms);
  });

  app.protect("/api/private", [](vix::Request &req, vix::Response &res, vix::App::Next next)
  {
    const std::string token = req.header("Authorization");

    if (token.empty())
    {
      res.status(401).json({
        "error", "missing authorization header"
      });
      return;
    }

    req.state().set(CurrentUser{"42"});

    next();
  });

  app.get("/", [](vix::Request &req, vix::Response &res)
  {
    (void)req;

    res.text("Home");
  });

  app.get("/api/private/me", [](vix::Request &req, vix::Response &res)
  {
    const auto &user = req.state().get<CurrentUser>();

    res.json({
      "id", user.id
    });
  });

  app.run(8080);

  return 0;
}

API summary ​

APIPurpose
app.use(middleware)Register global middleware.
app.use(prefix, middleware)Register prefix middleware.
app.protect(prefix, middleware)Protect a route prefix.
app.protect_exact(path, middleware)Protect one exact path.
Group::use(middleware)Register middleware for a group prefix.
Group::protect(prefix, middleware)Protect a group sub-prefix.
Group::protect_exact(path, middleware)Protect one exact group path.
vix::App::NextContinue to the next middleware or final handler.
req.state().set<T>(value)Store request-scoped data.
req.state().get<T>()Read required request-scoped data.
req.state().try_get<T>()Read optional request-scoped data.

Best practices ​

Register middleware before the routes that need it.

cpp
app.use("/admin", auth_middleware);
app.get("/admin/dashboard", dashboard_handler);

Keep middleware focused on one responsibility.

cpp
logging_middleware
auth_middleware
cors_middleware

Always call next() only when the request should continue.

cpp
if (!allowed)
{
  res.status(403).json({"error", "forbidden"});
  return;
}

next();

Use request state to pass data from middleware to handlers.

cpp
req.state().set(CurrentUser{"42"});

Use prefix middleware for sections of your app.

cpp
app.use("/api", api_middleware);
app.use("/admin", admin_middleware);

Next steps ​

Read the next pages:

Released under the MIT License.