Skip to content

Request Validation (HTTP)

This example shows how to use vix::validation inside HTTP routes.

Each section: - minimal main() - validation inside a route - structured JSON error response


1) Validate JSON body (POST /register)

Validates a JSON payload using BaseModel.

cpp
#include <vix.hpp>
#include <vix/validation/BaseModel.hpp>
#include <vix/validation/Schema.hpp>

using namespace vix;
namespace J = vix::json;

struct RegisterForm : vix::validation::BaseModel<RegisterForm>
{
  std::string email;
  std::string password;

  static vix::validation::Schema<RegisterForm> schema()
  {
    using namespace vix::validation;

    return schema<RegisterForm>()
      .field("email", &RegisterForm::email,
        field<std::string>().required().email())
      .field("password", &RegisterForm::password,
        field<std::string>().required().length_min(8));
  }
};

int main()
{
  App app;

  app.post("/register", [](Request& req, Response& res)
  {
    RegisterForm form;

    const auto& j = req.json();
    if (j.contains("email")) form.email = j["email"].get<std::string>();
    if (j.contains("password")) form.password = j["password"].get<std::string>();

    auto result = form.validate();

    if (!result.ok())
    {
      J::array errors;
      for (const auto& e : result.errors.all())
      {
        errors.push_back(J::obj({
          "field", e.field,
          "message", e.message
        }));
      }

      res.status(400).json({
        "ok", false,
        "errors", errors
      });
      return;
    }

    res.json({
      "ok", true,
      "message", "User registered"
    });
  });

  app.run(8080);
  return 0;
}

Test:

curl -X POST http://localhost:8080/register   -H "Content-Type: application/json"   -d '{"email":"bad","password":"123"}'

Validate query inputs using validate_parsed.

cpp
#include <vix.hpp>
#include <vix/validation/Pipe.hpp>

using namespace vix;
using namespace vix::validation;

int main()
{
  App app;

  app.get("/search", [](Request& req, Response& res)
  {
    auto page = validate_parsed<int>("page",
                   req.query_value("page", "1"))
                   .min(1)
                   .max(100)
                   .result("page must be a number");

    if (!page.ok())
    {
      res.status(400).json({
        "ok", false,
        "error", page.errors.all().front().message
      });
      return;
    }

    res.json({
      "ok", true,
      "page", req.query_value("page", "1")
    });
  });

  app.run(8080);
  return 0;
}

Test:

curl "http://localhost:8080/search?page=abc"

3) Validate path parameter (GET /users/{id})

cpp
#include <vix.hpp>
#include <vix/validation/Pipe.hpp>

using namespace vix;
using namespace vix::validation;

int main()
{
  App app;

  app.get("/users/{id}", [](Request& req, Response& res)
  {
    auto id = validate_parsed<int>("id", req.param("id"))
                .min(1)
                .result("id must be a positive number");

    if (!id.ok())
    {
      res.status(400).json({
        "ok", false,
        "error", id.errors.all().front().message
      });
      return;
    }

    res.json({
      "ok", true,
      "id", req.param("id")
    });
  });

  app.run(8080);
  return 0;
}

4) Form-style validation (POST /login)

Use Form<T> when binding raw input.

cpp
#include <vix.hpp>
#include <vix/validation/BaseModel.hpp>
#include <vix/validation/Schema.hpp>
#include <vix/validation/Form.hpp>

using namespace vix;

struct LoginForm : vix::validation::BaseModel<LoginForm>
{
  std::string email;
  std::string password;

  static vix::validation::Schema<LoginForm> schema()
  {
    using namespace vix::validation;

    return schema<LoginForm>()
      .field("email", &LoginForm::email,
        field<std::string>().required().email())
      .field("password", &LoginForm::password,
        field<std::string>().required().length_min(6));
  }
};

int main()
{
  App app;

  app.post("/login", [](Request& req, Response& res)
  {
    using Input = std::vector<std::pair<std::string_view, std::string_view>>;

    Input input = {
      {"email", req.query_value("email")},
      {"password", req.query_value("password")}
    };

    auto r = vix::validation::Form<LoginForm>::validate(input);

    if (!r)
    {
      res.status(400).json({
        "ok", false,
        "error", r.errors().all().front().message
      });
      return;
    }

    res.json({
      "ok", true,
      "message", "Login valid"
    });
  });

  app.run(8080);
  return 0;
}

Pattern Summary

Inside HTTP routes:

  • Bind request data
  • Call validation
  • If invalid → return 400 with structured errors
  • If valid → continue business logic

This keeps routing clean and validation explicit.