⬡ C++20 · v0.1.0, pre-1.0

A C++20 web framework that comes with the parts you'd otherwise assemble.

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.

C++20
Standard required
OpenSSL
Only hard dependency
HS256
JWT signing
v0.1.0
API may still change
main.cpp
#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
}
Boost.Asio C++20 Coroutines JWT + PBKDF2 PostgreSQL / Supabase Rate limiting & CORS Docker + Render

Install

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.

CMakeLists.txt
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)
  • Requires a C++20 compiler, CMake 3.16+ and OpenSSL. Building without auth? Pass -DENABLE_OPENSSL=OFF.
  • nlohmann/json is fetched automatically if find_package doesn't already resolve it.
  • Powered by Boost.Asio and Boost.Beast for modern C++20 coroutine networking.
  • A vcpkg manifest and a Conan recipe ship in the repo if you'd rather go through a package manager.

What Prism is, and what it isn't

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.

Built for: one binary, all the pieces

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.

Built for: small services and modern C++20

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.

Not built for: bare-metal extreme micro-benchmarks

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.

Not built for: a frozen 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.

What's in the box

Each of these is a component you'd otherwise pick, integrate and maintain yourself.

Routing

get / post / put / del / patch handlers. Path parameters like /posts/:id land in req.path_params; query strings land in req.query_params.

Middleware

Functions that run in registration order before your handler. Return std::nullopt to continue, or a response to short-circuit the chain.

Authentication

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.

JSON

A Json value type over nlohmann/json: Json::parse, subscript assignment, typed accessors that throw on mismatch, and dump().

Logging

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.

PostgreSQL

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.

CORS & rate limiting

Cross-origin headers driven by config, and a per-client-IP request limit with a configurable window.

Static files

serve_static() maps a route prefix onto a directory, so a single-page frontend can ship alongside the API.

Deployment

A multi-stage Dockerfile, a render.yaml, and a /health endpoint that reports database status for your load balancer.

The API in practice

Four things you'll write on day one.

routes.cpp
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();
json.cpp
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.cpp
// 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();
});
auth.cpp
// 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());
});

A worked example ships with the repo

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.

01 · Build

Configure and compile

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
02 · Containerize

Build the image

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
03 · Deploy

Push to Render

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

Add it to a CMakeLists.txt and see.

Free for personal projects, learning, research and nonprofits. Commercial use needs a license. Open an issue and we'll sort it out.