Skip to content

OpenAPI

vix::openapi provides OpenAPI generation and built-in API documentation routes for Vix Core.

Use it when you want to expose an OpenAPI 3 document from a Vix router and serve an offline Swagger UI page for your HTTP application.

Public header

cpp
#include <vix/openapi/OpenApi.hpp>

To register the documentation routes, include:

cpp
#include <vix/openapi/register_docs.hpp>

What OpenAPI provides

The OpenAPI module provides:

  • OpenAPI 3.0.3 JSON generation
  • route documentation from the Vix router
  • extra documentation registered by modules
  • stable operationId generation
  • default responses when no route response metadata exists
  • /openapi.json route registration
  • /docs Swagger UI route registration
  • embedded Swagger UI CSS and JavaScript assets
  • offline documentation UI with no external CDN dependency

Basic model

The OpenAPI generator reads route metadata from the router.

text
Router
  -> routes()
  -> RouteDoc
  -> OpenAPI JSON

It also reads extra docs from the global OpenAPI registry.

text
Registry
  -> extra module docs
  -> OpenAPI JSON

The final document is built as:

text
router routes + registry docs
  -> OpenAPI 3.0.3 document

Generate OpenAPI from a router

cpp
#include <vix.hpp>
#include <vix/openapi/OpenApi.hpp>

int main()
{
  vix::App app;

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

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

  auto doc = vix::openapi::build_from_router(
      *app.router(),
      "My API",
      "1.0.0");

  vix::print(doc.dump(2));

  return 0;
}

Register OpenAPI and docs routes

Use register_openapi_and_docs(...) to add built-in documentation routes to a router.

cpp
#include <vix.hpp>
#include <vix/openapi/register_docs.hpp>

int main()
{
  vix::App app;

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

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

  vix::openapi::register_openapi_and_docs(
      *app.router(),
      "My API",
      "1.0.0");

  app.run(8080);

  return 0;
}

Then open:

text
http://localhost:8080/openapi.json

or:

text
http://localhost:8080/docs

Registered routes

register_openapi_and_docs(...) registers these routes:

text
GET /openapi.json
GET /docs
GET /docs/
GET /docs/index.html
GET /docs/swagger-ui.css
GET /docs/swagger-ui-bundle.js

/openapi.json

The /openapi.json route returns the generated OpenAPI 3 document.

text
GET /openapi.json

Response content type:

text
application/json; charset=utf-8

The route also sets:

text
Cache-Control: no-store
X-Content-Type-Options: nosniff

/docs

The /docs route serves the Swagger UI page.

text
GET /docs

It renders the generated /openapi.json document.

The Swagger UI assets are served locally from embedded assets.

text
/docs/swagger-ui.css
/docs/swagger-ui-bundle.js

This means the docs UI can work without loading Swagger UI from an external CDN.

/docs/ and /docs/index.html

These routes serve the same docs page as /docs.

text
GET /docs/
GET /docs/index.html

They are registered directly to avoid redirect loops and to support static-like expectations.

OpenAPI document shape

The generated document starts with:

json
{
  "openapi": "3.0.3",
  "info": {
    "title": "Vix API",
    "version": "1.31.0"
  },
  "paths": {},
  "components": {}
}

The title and version can be customized.

cpp
auto doc = vix::openapi::build_from_router(
    router,
    "My API",
    "1.0.0");

Route documentation

OpenAPI metadata comes from vix::router::RouteDoc.

cpp
vix::router::RouteDoc doc;

doc.summary = "Get status";
doc.description = "Returns the current service status.";
doc.tags = {"system"};
doc.responses["200"] = {
  {"description", "OK"}
};

A RouteDoc can contain:

FieldPurpose
summaryShort one-line route summary.
descriptionLonger route description.
tagsOpenAPI tags.
request_bodyOpenAPI request body object.
responsesOpenAPI responses object.
xVendor-specific OpenAPI extensions.

RouteDoc example

cpp
vix::router::RouteDoc doc;

doc.summary = "Create user";
doc.description = "Creates a new user.";
doc.tags = {"users"};

doc.request_body = {
  {"required", true},
  {"content", {
    {"application/json", {
      {"schema", {
        {"type", "object"},
        {"properties", {
          {"name", {{"type", "string"}}}
        }}
      }}
    }}
  }}
};

doc.responses["201"] = {
  {"description", "User created"}
};

Register a documented route manually

Advanced code can register a route directly on the router with documentation metadata.

cpp
#include <vix.hpp>

int main()
{
  vix::App app;

  using Fn = std::function<void(vix::Request &, vix::Response &)>;

  auto handler = std::make_shared<vix::http::RequestHandler<Fn>>(
      "/status",
      [](vix::Request &req, vix::Response &res)
      {
        (void)req;

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

  vix::router::RouteDoc doc;
  doc.summary = "Service status";
  doc.description = "Returns whether the service is running.";
  doc.tags = {"system"};
  doc.responses["200"] = {{"description", "OK"}};

  app.router()->add_route(
      "GET",
      "/status",
      handler,
      vix::router::RouteOptions{},
      doc);

  app.run(8080);

  return 0;
}

Most applications should register routes through App.

Direct router registration is mainly useful for tooling, generated routes, or framework internals.

OpenAPI method conversion

method_to_openapi(...) converts HTTP methods to OpenAPI operation keys.

cpp
vix::openapi::method_to_openapi("GET");

Result:

text
get

Supported methods:

text
GET
POST
PUT
DELETE
PATCH
HEAD
OPTIONS
TRACE

Unsupported methods return an empty string.

Default responses

If a route has no response documentation, Vix adds a default response.

json
{
  "200": {
    "description": "OK"
  }
}

This is produced by:

cpp
vix::openapi::default_responses();

Operation IDs

Vix generates a stable operationId from method and path.

cpp
vix::openapi::make_operation_id("get", "/users/{id}");

Result shape:

text
get__users__id

The function keeps letters and numbers.

Other characters are replaced with _.

This gives client generators a stable operation name.

Build from router

Use build_from_router(...) to create the OpenAPI JSON document.

cpp
nlohmann::json doc = vix::openapi::build_from_router(
    router,
    "Vix API",
    "1.31.0");

Signature:

cpp
nlohmann::json build_from_router(
    const vix::router::Router &router,
    std::string title = "Vix API",
    std::string version = "1.31.0");

It includes:

  • routes declared in the HTTP router
  • extra docs registered in vix::openapi::Registry
  • default responses for undocumented responses
  • route summaries, descriptions, tags, request bodies, responses, and extensions

Extra route docs

Some modules may need to document routes that are not registered directly in the core router.

Use vix::openapi::Registry.

cpp
vix::router::RouteDoc doc;

doc.summary = "WebSocket endpoint";
doc.description = "Upgrades the connection to WebSocket.";
doc.tags = {"websocket"};
doc.responses["101"] = {{"description", "Switching Protocols"}};

vix::openapi::Registry::add(
    "GET",
    "/ws",
    doc);

The next call to build_from_router(...) includes this extra route documentation.

Registry

Registry is a global registry for additional OpenAPI route docs.

It provides:

cpp
vix::openapi::Registry::add(method, path, doc);
vix::openapi::Registry::snapshot();
vix::openapi::Registry::clear();

Use it for:

  • WebSocket docs
  • module docs
  • generated docs
  • routes managed outside the normal router
  • plugin-style integrations

ExtraRouteDoc

ExtraRouteDoc stores one extra documented route.

cpp
struct ExtraRouteDoc
{
  std::string method;
  std::string path;
  vix::router::RouteDoc doc;
};

Add extra docs

cpp
vix::router::RouteDoc doc;

doc.summary = "Health check";
doc.tags = {"system"};
doc.responses["200"] = {{"description", "OK"}};

vix::openapi::Registry::add("GET", "/health", doc);

Read registry snapshot

cpp
const auto docs = vix::openapi::Registry::snapshot();

for (const auto &entry : docs)
{
  vix::print(entry.method, entry.path);
}

snapshot() returns a copy, so it is safe to iterate without holding the registry lock.

Clear registry

cpp
vix::openapi::Registry::clear();

This removes all registered extra route docs.

It is useful for tests or when rebuilding docs in controlled environments.

Duplicate handling

When OpenAPI is built, duplicate route keys are ignored after the first occurrence.

The duplicate key is:

text
method + path

Example:

text
GET /status

If the same method and path appear more than once between the router and registry, only the first one is kept.

Vendor extensions

RouteDoc::x can store OpenAPI vendor extensions.

cpp
vix::router::RouteDoc doc;

doc.x["x-vix-runtime"] = "core";
doc.x["x-vix-heavy"] = true;

These fields are copied into the generated OpenAPI operation.

Result shape:

json
{
  "x-vix-runtime": "core",
  "x-vix-heavy": true
}

Request body metadata

Use RouteDoc::request_body to describe request bodies.

cpp
doc.request_body = {
  {"required", true},
  {"content", {
    {"application/json", {
      {"schema", {
        {"type", "object"},
        {"properties", {
          {"name", {{"type", "string"}}},
          {"email", {{"type", "string"}}}
        }}
      }}
    }}
  }}
};

This becomes:

json
{
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "type": "object"
        }
      }
    }
  }
}

Response metadata

Use RouteDoc::responses to describe possible responses.

cpp
doc.responses["200"] = {
  {"description", "OK"}
};

doc.responses["404"] = {
  {"description", "User not found"}
};

If responses is empty, Vix adds:

json
{
  "200": {
    "description": "OK"
  }
}

Tags

Use tags to group routes in Swagger UI.

cpp
doc.tags = {"users"};

Example with multiple tags:

cpp
doc.tags = {"users", "admin"};

Docs routes metadata

The docs routes also receive route documentation.

For example:

text
GET /openapi.json

has a docs tag and a summary like:

text
OpenAPI spec

This means the docs routes can appear in the generated OpenAPI document too.

Enable docs with vix run

vix run keeps OpenAPI/docs disabled by default.

To enable docs for one run:

bash
vix run api --docs

This sets:

bash
VIX_DOCS=1

To keep docs disabled:

bash
vix run api --no-docs

This sets:

bash
VIX_DOCS=0

During development:

bash
vix run api --docs

Then open:

text
http://localhost:8080/docs

For normal runs:

bash
vix run api

Docs stay disabled unless you explicitly enable them.

For production, expose docs only when needed.

Common approaches:

text
disable docs completely
protect /docs behind auth
serve docs only in internal environments
serve /openapi.json only in CI or staging

If docs are enabled publicly, make sure your API metadata does not expose sensitive internal details.

OpenAPI and App

In normal Vix Core, docs registration can be done through the router.

cpp
vix::openapi::register_openapi_and_docs(
    *app.router(),
    "My API",
    "1.0.0");

After registration:

text
/openapi.json
/docs
/docs/
/docs/index.html
/docs/swagger-ui.css
/docs/swagger-ui-bundle.js

are available.

Complete example

cpp
#include <vix.hpp>
#include <vix/openapi/register_docs.hpp>

int main()
{
  vix::App app;

  vix::router::RouteDoc status_doc;
  status_doc.summary = "Get status";
  status_doc.description = "Returns the current service status.";
  status_doc.tags = {"system"};
  status_doc.responses["200"] = {
    {"description", "OK"}
  };

  using Fn = std::function<void(vix::Request &, vix::Response &)>;

  auto status_handler = std::make_shared<vix::http::RequestHandler<Fn>>(
      "/api/status",
      [](vix::Request &req, vix::Response &res)
      {
        (void)req;

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

  app.router()->add_route(
      "GET",
      "/api/status",
      status_handler,
      vix::router::RouteOptions{},
      status_doc);

  vix::openapi::register_openapi_and_docs(
      *app.router(),
      "Example API",
      "1.0.0");

  app.run(8080);

  return 0;
}

Open:

text
http://localhost:8080/openapi.json

or:

text
http://localhost:8080/docs

API summary

APIPurpose
method_to_openapi(method)Convert an HTTP method to an OpenAPI operation key.
default_responses()Return the default 200 OK response object.
make_operation_id(method, path)Build a stable operation ID from method and path.
build_from_router(router, title, version)Build an OpenAPI 3 document from the router and registry.
register_openapi_and_docs(router, title, version)Register /openapi.json and /docs routes.
Registry::add(method, path, doc)Register extra route documentation.
Registry::snapshot()Return a copy of registered extra docs.
Registry::clear()Clear extra docs.

RouteDoc summary

FieldPurpose
summaryOne-line operation summary.
descriptionDetailed operation description.
tagsGrouping tags for the operation.
request_bodyOpenAPI request body metadata.
responsesOpenAPI responses metadata.
xVendor-specific extensions.

Header map

AreaHeader
OpenAPI generator<vix/openapi/OpenApi.hpp>
Registry<vix/openapi/Registry.hpp>
Docs route registration<vix/openapi/register_docs.hpp>
Route documentation<vix/router/RouteDoc.hpp>
Router<vix/router/Router.hpp>
Docs UI<vix/openapi/DocsUI.hpp>
Swagger assets<vix/openapi/assets/SwaggerAssets.hpp>

Best practices

Enable docs explicitly during development.

bash
vix run api --docs

Use clear route summaries.

cpp
doc.summary = "Create user";

Group routes with tags.

cpp
doc.tags = {"users"};

Always document expected responses.

cpp
doc.responses["200"] = {{"description", "OK"}};
doc.responses["404"] = {{"description", "Not found"}};

Use Registry for routes that are not directly stored in the core router.

cpp
vix::openapi::Registry::add("GET", "/ws", doc);

Avoid exposing sensitive internal information in public docs.

text
Keep production docs intentional.

Next steps

Read the related pages:

Released under the MIT License.