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().
| Member | Description |
|---|---|
| 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() const | Whether the listener is currently accepting. |
| void get(path, handler) | Registers a GET route. :name segments become path parameters. |
| void post / put / del / patch / options | Same, 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
| Field | Default | Notes |
|---|---|---|
| int port | 8080 | Listen port |
| std::string host | "0.0.0.0" | Bind address |
| int thread_pool_size | 4 | Worker threads handling requests |
| int max_connections | 1000 | Connection ceiling |
| int request_timeout_ms | 30000 | Per-request timeout |
| bool enable_cors | true | Emit CORS headers |
| bool enable_compression | false | Response compression |
HttpRequest
Handed to every handler and middleware by const reference.
| Field | Notes |
|---|---|
| HttpMethod method | GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD |
| std::string path | Request path, without the query string |
| unordered_map headers | Stored as received and lowercased, so lookups work whatever casing the client sent |
| unordered_map query_params | Parsed from the query string |
| unordered_map path_params | /posts/:id matched against /posts/7 gives path_params["id"] == "7" |
| std::string body | Raw request body |
| std::string remote_ip | Client 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.
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
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.
| Member | Description |
|---|---|
| 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() const | Throws 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() const | Throws JsonException if the value is not a boolean. |
| ArrayView as_array() const | Iterable 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) const | Serializes. indent > 0 pretty-prints. |
| void save_file(path, indent = 0) const | Writes 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 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.
| Member | Description |
|---|---|
| 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
| Type | Fields |
|---|---|
| AuthConfig | secret_key, token_expiry (1h), refresh_token_expiry (24h), enable_refresh_tokens |
| AuthResult | success, token, user, error_message |
| User | id, username, email, roles, created_at |
| TokenPayload | user_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:
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).
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.
// 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");
| Member | Description |
|---|---|
| Logger(name = "default", config = {}) | Construct directly, or use get_logger(). |
| const std::string& get_name() const | The name this logger was registered under. |
| trace / debug / info / warn / error / fatal | One 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
| Sink | Constructor | Notes |
|---|---|---|
| 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 field | Default | Notes |
|---|---|---|
| host / port / database / user / password | none | Ignored when DATABASE_URL is set |
| sslmode | "prefer" | Use "require" for hosted databases |
| pool_size | 10 | Connections held open |
| max_overflow | 5 | Extra connections beyond the pool. Acquiring past this throws. |
| connection_timeout_ms | 5000 | Connect 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.