Routing, middleware, JWT authentication, JSON, logging and PostgreSQL live behind a single header. You add one block to your CMakeLists.txt and start writing handlers. No vendoring, no dependency archaeology.
#include "Prism.hpp"
using namespace Prism;
int main() {
Server server;
server.get("/", [](const HttpRequest&) -> boost::asio::awaitable {
co_return HttpResponse::text("Hello, Prism!");
});
server.start(); // :8080
}
Add Prism from your CMakeLists.txt. CMake fetches and builds it as part of your own configure step. There is nothing to install system-wide first.
include(FetchContent)
FetchContent_Declare(
Prism
GIT_REPOSITORY https://github.com/Segniko/Prism.git
GIT_TAG v0.1.0 # pin a tag, main moves
GIT_SUBMODULES ""
)
FetchContent_MakeAvailable(Prism)
add_executable(my_app main.cpp)
target_link_libraries(my_app PRIVATE Prism::Prism)
-DENABLE_OPENSSL=OFF.find_package doesn't already resolve it.C++ has good web frameworks already. Prism makes a specific trade, and it's worth knowing whether that trade suits you before you install it.
If your service needs routing plus auth plus JSON plus logging plus a database, Prism gives you all of them from one #include, with one build system entry and one set of conventions.
Asynchronous coroutines over Boost.Asio. Straightforward to reason about, straightforward to debug, and fast enough for the load an internal API or a side project will ever see.
If you're chasing raw multi-million requests-per-second, look at specialized userspace-networking stacks instead. Prism prioritizes developer ergonomics, safety and a clean coroutine API.
Prism is pre-1.0. Signatures still move between versions. Pin a tag rather than tracking main, and read the changelog before you upgrade.
Each of these is a component you'd otherwise pick, integrate and maintain yourself.
get / post / put / del / patch handlers. Path parameters like /posts/:id land in req.path_params; query strings land in req.query_params.
Functions that run in registration order before your handler. Return std::nullopt to continue, or a response to short-circuit the chain.
HS256 JWTs with the algorithm pinned on verify, salted PBKDF2 password hashing, role checks, and token revocation that survives until a token's natural expiry.
A Json value type over nlohmann/json: Json::parse, subscript assignment, typed accessors that throw on mismatch, and dump().
Named loggers fanning out to console, file, rotating-file or callback sinks, with per-logger levels and a configurable message pattern. Line-flushed so logs appear under Docker.
A pooled libpqxx connection layer that creates its schema on first start. Point DATABASE_URL at Supabase and it works. Leave it unset and you get in-memory storage.
Cross-origin headers driven by config, and a per-client-IP request limit with a configurable window.
serve_static() maps a route prefix onto a directory, so a single-page frontend can ship alongside the API.
A multi-stage Dockerfile, a render.yaml, and a /health endpoint that reports database status for your load balancer.
Four things you'll write on day one.
Server server;
// Path parameters are parsed into req.path_params
server.get("/api/posts/:id", [](const HttpRequest& req) -> boost::asio::awaitable {
std::string id = req.path_params.at("id");
Json post;
post["id"] = id;
post["title"] = "Hello from Prism";
co_return HttpResponse::json(post.dump());
});
// Query strings, too: /api/posts?limit=10
server.get("/api/posts", [](const HttpRequest& req) -> boost::asio::awaitable {
auto limit = req.query_params.count("limit")
? req.query_params.at("limit") : "20";
co_return HttpResponse::json(R"({"limit":)" + limit + "}");
});
server.start();
server.post("/api/posts", [](const HttpRequest& req) -> boost::asio::awaitable {
Json body = Json::parse(req.body);
std::string title = body["title"].as_string();
std::string content = body["content"].as_string();
Json reply;
reply["success"] = true;
reply["title"] = title;
co_return HttpResponse::json(reply.dump(), 201);
});
// Middleware executes around the request. Return a response early to
// short-circuit, or call co_await next() to continue down the chain.
server.use([](HttpRequest& req, NextFn next) -> boost::asio::awaitable {
LOG_INFO("-> " + req.path);
co_return co_await next(); // continue
});
server.use([](HttpRequest& req, NextFn next) -> boost::asio::awaitable {
if (req.header("Authorization").empty())
co_return HttpResponse::error("Unauthorized", 401); // stop
co_return co_await next();
});
// A protected route: only requests with a valid Bearer token pass.
server.get("/api/auth/me", [&](const HttpRequest& req) -> boost::asio::awaitable {
std::string hdr = req.header("Authorization"); // "Bearer ..."
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("Invalid token", 401);
Json out;
out["id"] = res.user.id;
out["username"] = res.user.username;
co_return HttpResponse::json(out.dump());
});
Prism API is a blog backend built on the framework, with accounts, posts and comments. Clone the repo, build it standalone, and read ConfigAPI/src/main.cpp to see how the pieces fit together in something bigger than a snippet.
A standard CMake build produces a single prism-api binary.
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
./build/prism-api # http://localhost:8080
The multi-stage Dockerfile compiles in one layer and ships a slim runtime.
docker build -t prism-api .
docker run -p 8080:8080 \
-e JWT_SECRET_KEY=$(openssl rand -hex 32) \
-e DATABASE_URL=... prism-api
render.yaml is detected automatically. Set two secrets and deploy.
# Set in the Render dashboard:
# JWT_SECRET_KEY (openssl rand -hex 32)
# DATABASE_URL (Supabase session pooler)
git push # Render builds and deploys
Free for personal projects, learning, research and nonprofits. Commercial use needs a license. Open an issue and we'll sort it out.