Appearance
Core Overview
This page explains how the Vix.cpp core works at a high level. It is written for beginners coming from Python (FastAPI) or Node.js (Express).
What the core provides
Vix core is the runtime layer that makes these things work together:
- HTTP server (Boost.Asio + Boost.Beast)
- Per-connection session handling (timeouts, basic safety checks)
- Router (method + path matching, path params, optional heavy scheduling)
- Request and response wrappers (simple route authoring)
- Optional OpenAPI + Swagger UI (served locally with offline assets)
- Executor and thread pool utilities for heavy workloads and periodic jobs
In practice, you write routes. The core handles networking, parsing, dispatching, and concurrency.
Request lifecycle (end to end)
A single HTTP request goes through this flow:
- HTTPServer accepts a TCP connection.
- Session is created for that socket.
- Session reads and parses an HTTP request (Beast parser).
- Session applies basic checks:
- request body limit
- timeout timer
- optional WAF rules (URL and body patterns, configurable)
- Session asks the Router to match
method + path. - If the route is marked heavy, Session posts it to the Executor.
- If not heavy, Session runs the route immediately on the I/O thread.
- The route handler writes a response.
- Session writes the response back to the client.
- If
keep-aliveis enabled, the same Session reads the next request on that socket.
Why this architecture exists
If you come from Node.js:
- Node has an event loop. Vix uses Asio I/O threads for networking.
- You still want to avoid blocking I/O threads with CPU or DB-heavy work.
- Heavy routes get scheduled on a dedicated executor so the server stays responsive.
If you come from Python:
- FastAPI uses an async event loop and thread pools for blocking work.
- Vix splits the same idea into I/O threads (network) and an executor (heavy tasks).
Core components
HTTPServer
Role: Owns the io_context, TCP acceptor, and I/O threads.
Key behaviors:
- Initializes the acceptor and binds to the configured port.
- Starts an async accept loop (
start_accept()). - Launches multiple I/O worker threads (
io_context->run()). - Spawns a
Sessionper connection. - Runs a metrics monitor that periodically logs executor metrics.
Useful details:
- If port is
0, the OS picks an ephemeral port. You can read it viabound_port(). calculate_io_thread_count()usesconfig.io_threadsif set, otherwise uses hardware concurrency.stop_async()closes the acceptor and stops the io_context.stop_blocking()waits for executor idle then joins threads.
Session
Role: Owns a single client connection, reads requests, routes them, writes responses.
Key behaviors:
- Uses a request parser and enforces
MAX_REQUEST_BODY_SIZE. - Starts a per-request timeout timer and closes slow/stalled connections.
- Optional WAF checks:
- WAF mode:
off,basic,strict - URL checks: length, null bytes, CR/LF, suspicious tokens
- Body checks for mutating methods (POST/PUT/PATCH/DELETE)
- WAF mode:
- Uses a strand for serialized socket writes, so responses remain ordered and thread-safe.
- Heavy routes are executed on the executor, then written back to the client on the strand.
Important mental model:
- A Session is like a single
req/resloop bound to one socket. - It may handle multiple requests if keep-alive is enabled.
Router
Role: Matches (HTTP method, path) and dispatches to the registered handler.
Core ideas:
- Internally it uses a route tree, keyed by
METHOD + /path/segments. - Path parameters are represented as
{name}. They are stored in the tree using a wildcard key. - It supports HEAD fallback: if a HEAD route is not found, it can reuse GET.
- It has a customizable not-found handler (default is JSON 404).
Heavy routes
A route can be marked as heavy using RouteOptions{ .heavy = true }.
What it means:
- Session will schedule the route on the executor.
- This is the recommended way to isolate CPU-heavy or DB-heavy handlers.
- The Router also exposes this in docs via
x-vix-heavy: true.
OpenAPI and Docs
Vix can expose:
GET /openapi.jsongenerated from the Router metadata and any extra docs registered by modules.- Swagger UI at:
GET /docsGET /docs/GET /docs/index.html
- Offline assets:
/docs/swagger-ui.css/docs/swagger-ui-bundle.js
The UI is served locally, with Vix theme tokens, so the docs can work even without external CDNs.
Disable auto docs
If you want to disable automatic docs routes for a run, use:
bash
vix run api.cpp --no-docsExecutor, ThreadPool, and timers
Vix core supports separating lightweight request handling from heavy work.
ThreadPool
Role: Priority-based pool that executes tasks concurrently.
Key behaviors:
- Tasks have:
- a function
- a priority
- a sequence number for stable ordering
- The pool can grow threads up to
maxThreadswhen saturated and backlog increases. TaskGuardtracks active tasks using RAII.- The pool can schedule periodic jobs via
periodicTask(). - Metrics snapshot:
- pendingTasks
- activeTasks
- timedOutTasks
When to use it:
- CPU heavy operations (hashing, image work, large JSON processing)
- DB work (if your DB client is blocking)
- Any expensive logic you do not want on I/O threads
interval() timer
Role: Simple repeating timer that posts work onto an executor.
interval() creates a tiny worker thread that wakes up every period and posts a task. It returns an RAII handle that stops and joins on destruction.
When to use it:
- periodic metrics logging
- cache refresh jobs
- background cleanup tasks
Important rule:
- Keep the scheduled function fast, or schedule into a heavy executor if needed.
Mental mapping for Node.js and Python developers
Node.js mapping (Express)
app.get("/path", handler)feels like Express.Requestis likereq.Responseis likeres.- Heavy route scheduling is like pushing work into a worker pool.
Python mapping (FastAPI)
- Routes are simple functions.
- Returning a JSON-like object can auto-send.
- Heavy routes are conceptually similar to using thread pools for blocking work.
Minimal example
cpp
#include <vix.hpp>
using namespace vix;
int main()
{
App app;
app.get("/", [](Request&, Response& res){
res.json({"message", "Hello, Vix!"});
});
app.run(8080);
return 0;
}Practical recommendations
- Keep I/O threads clean: do not block them with CPU-heavy work.
- Mark heavy endpoints as heavy and use an executor.
- Use
keep-alivewhere appropriate for performance. - Keep responses deterministic and always set proper status codes.
- Start with the docs routes enabled. They help beginners discover the API quickly.