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

Routes and Views

The web template connects C++ route handlers to HTML templates. A route receives the request, prepares a template context, and renders a file from views/. The view then uses layouts, includes, variables, and loops to produce the final HTML response.

This page focuses on the generated page routes and the template files they render.

txt
RouteRegistry
  -> PageController
      -> vix::template_::Context
      -> views/index.html
      -> views/dashboard.html

Generated routes

A new web project starts with three routes.

txt
GET /             HTML home page
GET /dashboard    HTML dashboard page
GET /health       JSON health check

The first two routes render HTML. They are owned by PageController.

txt
src/site/presentation/controllers/PageController.cpp

The health route returns JSON. It is owned by HealthController.

txt
src/site/presentation/controllers/HealthController.cpp

This separation keeps browser pages and operational checks clear. Page routes render templates. Health routes give infrastructure a simple endpoint to check.

RouteRegistry

The generated route registry connects the controllers to the running application.

txt
include/site/presentation/routes/RouteRegistry.hpp
src/site/presentation/routes/RouteRegistry.cpp

The implementation is intentionally small.

cpp
void RouteRegistry::register_all(vix::App &app)
{
  controllers::PageController::register_routes(app);
  controllers::HealthController::register_routes(app);
}

AppBootstrap calls the registry during startup.

cpp
presentation::routes::RouteRegistry::register_all(app);

This keeps startup readable. The bootstrap knows when routes are registered, but the registry owns which base controllers are connected.

PageController

PageController owns the browser-facing page routes.

txt
include/site/presentation/controllers/PageController.hpp
src/site/presentation/controllers/PageController.cpp

The generated controller exposes one route registration function.

cpp
namespace site::presentation::controllers
{
  class PageController
  {
  public:
    static void register_routes(vix::App &app);
  };
}

The controller is the place where C++ prepares the data needed by a page. It should not build large HTML strings by hand. It should collect values, put them into a template context, and render the correct view.

Home route

The generated home route handles:

txt
GET /

It creates a template context and renders index.html.

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

  vix::template_::Context ctx;
  ctx.set("title", "Home");
  ctx.set("app_name", "site");
  ctx.set("user", "Guest");

  res.render("index.html", ctx);
});

The route handler decides which values the view needs. The template decides how those values are displayed.

Dashboard route

The generated dashboard route handles:

txt
GET /dashboard

It demonstrates a context with simple values and an array.

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

  vix::template_::Context ctx;
  ctx.set("title", "Dashboard");
  ctx.set("app_name", "site");
  ctx.set("user", "Guest");
  ctx.set("total_orders", 42);

  vix::template_::Array features;
  features.emplace_back("Server-rendered HTML");
  features.emplace_back("Layouts with extends");
  features.emplace_back("Partials with include");
  features.emplace_back("Static assets");

  ctx.set("features", features);

  res.render("dashboard.html", ctx);
});

This example shows the normal pattern for server-rendered pages. The controller prepares the page data in C++, then the view renders that data with template syntax.

Views directory

The generated views live in:

txt
views/
  base.html
  header.html
  index.html
  dashboard.html

AppBootstrap registers the directory during startup.

cpp
app.templates("views");

After this call, a route can render a view by name.

cpp
res.render("index.html", ctx);

The template name is resolved relative to the configured views/ directory.

txt
views/index.html

Base layout

The generated base layout is:

txt
views/base.html

It contains the shared HTML document structure.

html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>{{ title }} - {{ app_name }}</title>
    <link rel="stylesheet" href="/app.css" />
  </head>
  <body>
    {% include "header.html" %}

    <main class="page">{% block content %}{% endblock %}</main>

    <script src="/app.js"></script>
  </body>
</html>

The base layout keeps repeated HTML in one place. It loads the shared CSS and JavaScript, includes the header, and exposes a content block for page templates.

Header partial

The generated header lives in:

txt
views/header.html

It is included from base.html.

html
<header class="site-header">
  <a class="brand" href="/">{{ app_name }}</a>

  <nav class="nav">
    <a href="/">Home</a>
    <a href="/dashboard">Dashboard</a>
    <a href="/health">Health</a>
  </nav>
</header>

A partial is useful for shared markup that should appear on multiple pages. In this template, the header partial keeps navigation outside individual page templates.

Home view

The generated home view is:

txt
views/index.html

It extends the base layout and fills the content block.

html
{% extends "base.html" %} {% block content %}
<section class="hero">
  <p class="eyebrow">Vix Web</p>
  <h1>{{ title }}</h1>
  <p class="lead">
    Hello {{ user }}. Your Vix web backend is rendering HTML with layouts,
    includes, variables, and static assets.
  </p>

  <div class="actions">
    <a class="button primary" href="/dashboard">Open dashboard</a>
    <a class="button secondary" href="/health">Check health</a>
  </div>
</section>
{% endblock %}

The view receives title, app_name, and user from the C++ route handler. The base layout uses title and app_name for the document title, while the page content uses title and user.

Dashboard view

The generated dashboard view is:

txt
views/dashboard.html

It also extends the base layout.

html
{% extends "base.html" %} {% block content %}
<section class="card">
  <p class="eyebrow">Dashboard</p>
  <h1>{{ title }}</h1>
  <p class="lead">Welcome back, {{ user }}.</p>

  <div class="stat">
    <span>Total orders</span>
    <strong>{{ total_orders }}</strong>
  </div>

  <h2>Enabled features</h2>

  <ul class="feature-list">
    {% for feature in features %}
    <li>{{ feature }}</li>
    {% endfor %}
  </ul>
</section>
{% endblock %}

This page demonstrates a loop.

html
{% for feature in features %}
<li>{{ feature }}</li>
{% endfor %}

The features value comes from the controller.

cpp
vix::template_::Array features;
features.emplace_back("Server-rendered HTML");
features.emplace_back("Layouts with extends");
features.emplace_back("Partials with include");
features.emplace_back("Static assets");

ctx.set("features", features);

This keeps page data in C++ and page presentation in HTML.

Public assets used by views

The generated base layout loads files from public/.

html
<link rel="stylesheet" href="/app.css" />
<script src="/app.js"></script>

Those paths work because AppBootstrap mounts the public directory at /.

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

The generated files are:

txt
public/
  app.css
  app.js

Because public/ is mounted at /, the browser loads them as:

txt
/app.css
/app.js

Do not write /public/app.css in the generated layout unless the mount path is changed.

Health route

The health route is not a page view.

txt
GET /health

It returns JSON from HealthController.

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

  res.json({
    "ok", true,
    "status", "ok",
    "service", "site",
    "template", "web"
  });
});

Keep this route separate from page rendering. It exists for local checks, deployment scripts, reverse proxies, and monitoring tools.

Adding a new page

To add a new page, create a new view under views/.

txt
views/about.html
html
{% extends "base.html" %} {% block content %}
<section class="card">
  <p class="eyebrow">About</p>
  <h1>{{ title }}</h1>
  <p class="lead">{{ message }}</p>
</section>
{% endblock %}

Then add the route in PageController.

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

  vix::template_::Context ctx;
  ctx.set("title", "About");
  ctx.set("app_name", "site");
  ctx.set("message", "This page is rendered by Vix.");

  res.render("about.html", ctx);
});

Add the link to the shared header when the page should appear in navigation.

html
<a href="/about">About</a>

No new C++ source file is needed in this case, so vix.app does not need to change. The new HTML file is inside views/, and the whole views/ directory is already copied as a runtime resource.

Adding a new page controller

When one controller becomes too large, create another controller for a page group.

txt
include/site/presentation/controllers/AdminController.hpp
src/site/presentation/controllers/AdminController.cpp

Connect it through the route registry.

cpp
#include <site/presentation/controllers/AdminController.hpp>

void RouteRegistry::register_all(vix::App &app)
{
  controllers::PageController::register_routes(app);
  controllers::HealthController::register_routes(app);
  controllers::AdminController::register_routes(app);
}

Then add the new implementation file to the web manifest.

ini
sources = [
  "src/main.cpp",
  "src/site/app/AppBootstrap.cpp",
  "src/site/presentation/routes/RouteRegistry.cpp",
  "src/site/presentation/middleware/MiddlewareRegistry.cpp",
  "src/site/presentation/controllers/PageController.cpp",
  "src/site/presentation/controllers/HealthController.cpp",
  "src/site/presentation/controllers/AdminController.cpp",
]

Every .cpp file compiled into the application must be listed in vix.app.

Adding shared view parts

Shared HTML should live in partials.

For example:

txt
views/footer.html
html
<footer class="site-footer">
  <p>&copy; {{ app_name }}</p>
</footer>

Then include it from the base layout.

html
{% include "footer.html" %}

This keeps repeated layout pieces out of individual pages. Page templates should focus on their own content.

Adding static assets

Add CSS, JavaScript, images, or other public files under public/.

txt
public/
  app.css
  app.js
  logo.svg
  images/
    hero.png

Because public/ is mounted at /, those files are loaded with root paths.

html
<img src="/logo.svg" alt="{{ app_name }}" />

For nested assets:

html
<img src="/images/hero.png" alt="Hero" />

No manifest change is needed when files are added under the existing public/ resource directory.

Runtime resource copying

The generated web manifest declares runtime resources.

ini
resources = [
  ".env=.env",
  "public=public",
  "views=views",
  "storage=storage",
]

This means the runtime output can contain:

txt
bin/
  site
  .env
  public/
  views/
  storage/

If a page renders correctly from the source tree but fails after build or run, check whether the resource was copied to the runtime output. The view must be available beside the executable, not only in the project root.

Controller responsibilities

A page controller should prepare data and choose the view.

Good responsibilities include:

txt
read request values
load page data
build a template context
choose the template
render the response
return redirects or errors

Avoid using the controller to generate large HTML strings. The template files exist so the HTML remains readable and editable.

View responsibilities

A view should present data.

Good responsibilities include:

txt
HTML structure
layout extension
partials
blocks
variables
loops
small presentation conditions

Avoid hiding complicated application decisions inside the template. When a decision is important to the application, make it in C++ and pass the result to the view.

Common mistakes

The most common mistake is adding a new C++ controller and forgetting to add its .cpp file to vix.app. The header can be found and the file can exist, but the implementation will not be compiled unless the manifest lists it.

Another mistake is using the wrong asset path. In the generated web template, public/ is mounted at /, so public/app.css is requested as /app.css.

A third mistake is placing business logic in templates. Templates should render what the controller gives them. They should not become the place where application behavior is hidden.

A fourth mistake is forgetting that views/ and public/ are runtime resources. If they are missing beside the built target, the application may not find templates or assets even though the source tree looks correct.

Use PageController for page routes, HealthController for operational checks, RouteRegistry for wiring controllers, views/ for HTML templates, and public/ for browser assets. Add new HTML and assets freely under those resource directories. Add new .cpp files to vix.app whenever the web application gains new C++ implementation files.

Next step

Continue with production files to understand how the web template uses .env.example, vix.json, runtime resources, and production metadata.

Production Files

Released under the MIT License.