API Reference

Every public type in the framework, as declared in ConfigAPI/include/. Including Prism.hpp brings in all of them.

Prism is pre-1.0, so these signatures can change between versions. Pin a release tag rather than tracking main. Everything below lives in namespace Prism.

Server

Owns the listener, the route table and the middleware chain. Construct one, register routes, call start().

MemberDescription
Server(config = {}, version = "1.0.0")Constructs a server. The version string is reported by the built-in info route.
void start()Binds and listens. Blocks until stop() is called. Throws on bind failure.
void stop()Stops the listener and unblocks start().
bool is_running() constWhether the listener is currently accepting.
void get(path, handler)Registers a GET route. :name segments become path parameters.
void post / put / del / patch / optionsSame, for the other verbs. Note del, not delete. The latter is a keyword.
void use(middleware)Appends middleware. They run in registration order, before any handler.
void serve_static(route_path, fs_path)Serves a directory under a route prefix.
void set_config(config)
ServerConfig get_config() const
std::string get_version() const
Configuration accessors.

set_config() after start() has no effect on the running listener. Configure before starting.

ServerConfig

FieldDefaultNotes
int port8080Listen port
std::string host"0.0.0.0"Bind address
int thread_pool_size4Worker threads handling requests
int max_connections1000Connection ceiling
int request_timeout_ms30000Per-request timeout
bool enable_corstrueEmit CORS headers
bool enable_compressionfalseResponse compression

HttpRequest

Handed to every handler and middleware by const reference.

FieldNotes
HttpMethod methodGET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD
std::string pathRequest path, without the query string
unordered_map headersStored as received and lowercased, so lookups work whatever casing the client sent
unordered_map query_paramsParsed from the query string
unordered_map path_params/posts/:id matched against /posts/7 gives path_params["id"] == "7"
std::string bodyRaw request body
std::string remote_ipClient address, which the rate limiter keys on
std::string header(name, default = "")Case-insensitive lookup with a fallback

HttpResponse

Defaults to status 200 with Content-Type: application/json. Prefer the static builders.

response.cpp
HttpResponse::json(body, code = 200);    // Content-Type: application/json
HttpResponse::text(body, code = 200);    // Content-Type: text/plain
HttpResponse::error(message, code = 500);

// Or build one by hand:
HttpResponse resp(201, "Created");
resp.headers["Location"] = "/api/posts/7";
resp.body = post.dump();

Handlers & middleware

types.hpp
using RouteHandler = std::function<boost::asio::awaitable<HttpResponse>(const HttpRequest&)>;
using NextFn       = std::function<boost::asio::awaitable<HttpResponse>()>;
using Middleware   = std::function<boost::asio::awaitable<HttpResponse>(HttpRequest&, NextFn)>;

Middleware executes around the request in an onion model, awaiting co_await next() to proceed down the chain or returning early to short-circuit.

Json

A thin value type over nlohmann/json. Header-only.

MemberDescription
static Json parse(str)Parses a string. Throws JsonException on malformed input.
static Json load_file(path)Reads and parses a file. Throws JsonException if it can't be opened or parsed.
static Json object() / array()
json_object() / json_array()
Empty container of the given kind. The free functions are shorthand.
operator[](key) / operator[](index)Read or assign. Assignment on a fresh Json makes it an object.
std::string as_string() constThrows JsonException if the value is not a string.
int as_int() const
double as_double() const
Throw JsonException if the value is not a number.
bool as_bool() constThrows JsonException if the value is not a boolean.
ArrayView as_array() constIterable view. Must not outlive the Json it came from.
is_null / is_bool / is_number
is_string / is_array / is_object
Type predicates. Check before calling an as_* accessor if the shape isn't guaranteed.
bool has(key) const
bool empty() const
size_t size() const
Inspection.
void push_back(item)Appends to an array.
std::string dump(indent = 0) constSerializes. indent > 0 pretty-prints.
void save_file(path, indent = 0) constWrites the serialized value to a file.
nlohmann::json& get()Escape hatch to the underlying library value.

There is no Json::set(). Assign through the subscript: obj["key"] = value;. Note also that a subscript assignment can invalidate references previously taken into the same object.

json.cpp
Json user = Json::parse(R"({"name":"Alice","age":30,"tags":["a","b"]})");

std::string name = user["name"].as_string();
int age          = user["age"].as_int();

Json out = json_object();
out["ok"]    = true;
out["name"]  = name;
out["count"] = static_cast<int>(user["tags"].as_array().size());

return HttpResponse::json(out.dump());

AuthService

Issues and verifies HS256 JWTs and authenticates users against an IUserDatabase.

MemberDescription
AuthService(config = {}, user_db = nullptr)A null user_db gives you an InMemoryUserDatabase.
AuthResult register_user(username, email, password)Creates the account and returns a token. Fails if the username or email is taken.
AuthResult login(username_or_email, password)Accepts either identifier.
AuthResult verify_token(token)Checks revocation, signature, algorithm and expiry. Returns the user on success.
AuthResult refresh_token(token)Exchanges a still-valid token for a fresh one.
void logout(token)Revokes the token until its natural expiry, after which the entry is pruned.
bool has_role(payload, role)
bool has_any_role(payload, roles)
Role checks against a decoded payload.

Revocation is held in process memory. It does not survive a restart, and it is not shared across replicas. If you run more than one instance, a logout only applies to the instance that served it.

Auth types

TypeFields
AuthConfigsecret_key, token_expiry (1h), refresh_token_expiry (24h), enable_refresh_tokens
AuthResultsuccess, token, user, error_message
Userid, username, email, roles, created_at
TokenPayloaduser_id, username, roles, expires_at

AuthConfig ships a placeholder secret_key so tests run out of the box. Set your own before deploying anything. The bundled reference API refuses to start in ENVIRONMENT=production without a real JWT_SECRET_KEY; your own app should do the same.

Guarding routes

create_auth_middleware() returns an AuthMiddleware, which is std::optional<std::string>(const std::string&) and maps a token to a user id. That signature is deliberately independent of HTTP, so it is not directly usable with Server::use. Wrap it, or call verify_token inline:

guard.cpp
AuthService auth;

server.use([&](HttpRequest& req, NextFn next) -> boost::asio::awaitable<HttpResponse> {
    if (req.path.starts_with("/api/admin")) {
        std::string hdr = req.header("Authorization");
        std::string token = hdr.rfind("Bearer ", 0) == 0 ? hdr.substr(7) : hdr;

        AuthResult res = auth.verify_token(token);
        if (!res.success)
            co_return HttpResponse::error(res.error_message, 401);
    }

    co_return co_await next();   // authenticated, continue
});

IUserDatabase

Implement this to store users wherever you like. Two implementations ship with Prism: InMemoryUserDatabase (non-persistent, mutex-protected, used by default) and DatabaseUserDatabase (PostgreSQL, compiled in when ENABLE_POSTGRESQL is on).

IUserDatabase.hpp
virtual std::optional<User> find_by_username(const std::string& username) = 0;
virtual std::optional<User> find_by_email(const std::string& email)       = 0;
virtual std::optional<User> find_by_id(const std::string& id)             = 0;
virtual bool verify_password(const User& user, const std::string& password) = 0;
virtual User create_user(const std::string& username,
                         const std::string& email,
                         const std::string& password) = 0;

Passwords are stored as pbkdf2$<iterations>$<salt_hex>$<hash_hex>. Legacy unsalted SHA-256 digests still verify, so older stores keep working.

Logger

Loggers are named and retrieved with get_logger(name), which creates one on first use. Methods are instance methods. There is no static logging API.

logging.cpp
// Macros capture __FILE__, __LINE__ and __FUNCTION__ for you:
LOG_INFO("Server starting on port 8080");
LOG_WARN("Rate limit approaching for " + req.remote_ip);
LOG_ERROR("Database connection failed");

// Or address a named logger directly:
auto& log = get_logger("auth");
log.set_level(LogLevel::DEBUG);
log.debug("token verified");
MemberDescription
Logger(name = "default", config = {})Construct directly, or use get_logger().
const std::string& get_name() constThe name this logger was registered under.
trace / debug / info / warn / error / fatalOne method per level. LOG_* macros wrap these.
void log(level, message, file, line, function)The general form the others delegate to.
void set_level(level)
LogLevel get_level() const
Messages below the level are dropped.
void add_sink(sink)
void clear_sinks()
void flush_all()
Sink management. A console sink is added by default.

LoggerConfig fields: min_level (INFO), include_timestamp (true), include_thread_id (false), include_location (false), pattern ({timestamp} [{level}] {message}). Pattern placeholders: {timestamp} {level} {message} {thread} {file} {line} {function}. The last four only render when the matching config flag is on.

Log sinks

SinkConstructorNotes
ConsoleSink(bool use_colors = true)Flushes per line, so output appears immediately when stdout is piped (Docker, Render)
FileSink(path, bool append = true)Appends to one file
RotatingFileSink(base_path, max_size = 10MB, backups = 5)Rotates to .1, .2 and so on when it overflows
FunctionSink(std::function<void(const LogEntry&)>)Route entries anywhere, such as an external service or a test buffer

Implement ILogSink (write(const LogEntry&) and flush()) for anything else.

Database

Compiled in only when Prism is built with -DENABLE_POSTGRESQL=ON, which requires libpqxx. It is the persistence layer behind the bundled reference API rather than a general-purpose ORM: it owns a pooled libpqxx connection set, creates its schema on first start, and exposes the user/post/comment operations that API needs.

DatabaseConfig fieldDefaultNotes
host / port / database / user / passwordnoneIgnored when DATABASE_URL is set
sslmode"prefer"Use "require" for hosted databases
pool_size10Connections held open
max_overflow5Extra connections beyond the pool. Acquiring past this throws.
connection_timeout_ms5000Connect timeout

For anything beyond the reference API's needs, talk to libpqxx directly. Prism does not try to be your data-access layer.

Something here wrong or missing?

This page tracks the headers by hand. If it has drifted, the headers win, and an issue is welcome.