This is a C++ library originally written for accessing GitHub REST API v3. It is now organized to work with any REST API.
It supports three interchangeable HTTP backends for connecting to remote API servers:
Qt 6/5, libcurl and cpp-httplib. All backends expose the same IConnection API,
so applications can choose the HTTP stack that best fits their existing dependencies.
- CMake 3.16 or newer.
- A C++23 compiler. The public API uses C++23 library facilities such as
std::expected. - At least one HTTP backend must be enabled at configure time.
| Backend option | Public factory header | Factory function | External dependency |
|---|---|---|---|
CppRestAPI_QtBackend=ON |
<cpp_restapi/create_qt_connection.hpp> |
cpp_restapi::createQtConnection() |
Qt Network/Core |
CppRestAPI_CurlBackend=ON |
<cpp_restapi/create_curl_connection.hpp> |
cpp_restapi::createCurlConnection() |
libcurl |
CppRestAPI_CppHttplibBackend=ON |
<cpp_restapi/create_cpp-httplib_connection.hpp> |
cpp_restapi::createCppHttplibConnection() |
cpp-httplib |
Typical applications enable one backend: for example, Qt applications usually use the Qt backend,
while non-Qt applications can choose libcurl or cpp-httplib. Multiple backends can be enabled in
one build because each backend provides its own factory function behind the same IConnection
interface. This is mainly useful for examples, tests or comparing backends rather than for normal
application builds. If you do enable more than one backend, all corresponding dependencies must be
available to CMake.
The Qt backend uses Qt 6 by default and falls back to Qt 5 when Qt 6 is not found.
Set CppRestAPI_UseQt5 CMake variable to TRUE to force Qt 5 usage when both versions are available.
This is a CMake-based project and is primarily meant to be included as a subproject.
Simply embed cpp_restapi's sources in your project,
choose the HTTP backend you want to use and include the cpp_restapi project in your CMakeLists.txt like this:
# Pick one backend for a typical application.
set(CppRestAPI_CurlBackend ON) # libcurl backend
# set(CppRestAPI_QtBackend ON) # Qt backend
# set(CppRestAPI_CppHttplibBackend ON) # cpp-httplib backend
add_subdirectory(cpp_restapi)Then you can link your application against cpp_restapi:
target_link_libraries(app
PRIVATE
cpp_restapi
)cpp_restapi::LinkHeaderPaginationStrategy is an RFC 5988 Link-header based
pagination strategy with JSON-aware merging (concatenates arrays, deep-merges
objects).
This is useful for REST APIs that split large list responses into pages. For example,
GitHub returns only one page of issues, releases or repositories at a time and exposes
the next page through the HTTP Link header. The pagination strategy lets the library
follow those links and return one merged response instead of forcing each caller to
repeat the same "read header, fetch next page, merge JSON" loop.
It is built by default (controlled by the CppRestAPI_JsonPagination CMake option)
because the GitHub helpers use it for paginated endpoints. It adds a dependency on the
jsoncpp library. To drop the jsoncpp dependency entirely, disable both JSON pagination and GitHub helpers:
-DCppRestAPI_GitHub=OFF -DCppRestAPI_JsonPagination=OFF.
cpp_restapi::GitHub::ConnectionBuilder and cpp_restapi::GitHub::Request
are convenience wrappers for the GitHub REST API. They are built by default
(controlled by the CppRestAPI_GitHub CMake option; enabling it automatically
enables CppRestAPI_JsonPagination). Disable with -DCppRestAPI_GitHub=OFF
if not needed.
#include <iostream>
#include <cpp_restapi/create_curl_connection.hpp>
#include <cpp_restapi/iconnection.hpp>
int main(int argc, char** argv)
{
// Access The Star Wars API
auto connection = cpp_restapi::createCurlConnection("https://swapi.dev/api", {});
// fetch() returns std::expected<std::string, HttpError>
for (const auto& endpoint: {"people/1", "starships/12/"})
{
const auto result = connection->fetch(endpoint);
if (result)
std::cout << result.value() << '\n';
else
std::cerr << "Error " << result.error().statusCode << ": " << result.error().message << '\n';
}
return 0;
}This example accesses The Star Wars API using the libcurl backend.
fetch() returns std::expected<std::string, HttpError> — on success the response body is available via value(), on failure the HttpError carries the HTTP status code, response body and a human-readable message.
#include <iostream>
#include <QCoreApplication>
#include <QNetworkAccessManager>
#include <cpp_restapi/create_qt_connection.hpp>
#include <cpp_restapi/iconnection.hpp>
int main(int argc, char** argv)
{
QCoreApplication qapp(argc, argv);
QNetworkAccessManager manager;
// Access The Star Wars API
auto connection = cpp_restapi::createQtConnection(manager, "https://swapi.dev/api", {});
// fetch() returns std::expected<std::string, HttpError>
for (const auto& endpoint: {"people/1", "starships/12/"})
{
const auto result = connection->fetch(endpoint);
if (result)
std::cout << result.value() << '\n';
else
std::cerr << "Error " << result.error().statusCode << ": " << result.error().message << '\n';
}
return 0;
}#include <iostream>
#include <cpp_restapi/create_cpp-httplib_connection.hpp>
#include <cpp_restapi/iconnection.hpp>
int main(int argc, char** argv)
{
// Access The Star Wars API
auto connection = cpp_restapi::createCppHttplibConnection("https://swapi.dev/api", {});
// fetch() returns std::expected<std::string, HttpError>
for (const auto& endpoint: {"people/1", "starships/12/"})
{
const auto result = connection->fetch(endpoint);
if (result)
std::cout << result.value() << '\n';
else
std::cerr << "Error " << result.error().statusCode << ": " << result.error().message << '\n';
}
return 0;
}For accessing the GitHub API it is possible to use exactly the same approach as presented above.
However, for convenience, there are also additional helpers available:
#include <QCoreApplication>
#include <QDebug>
#include <QNetworkAccessManager>
#include <utility>
#include <cpp_restapi/create_qt_connection.hpp>
#include <cpp_restapi/github/connection_builder.hpp>
#include <cpp_restapi/github/request.hpp>
int main(int argc, char** argv)
{
QCoreApplication qapp(argc, argv);
QNetworkAccessManager manager;
auto connection = cpp_restapi::GitHub::ConnectionBuilder().build(cpp_restapi::createQtConnection, manager);
cpp_restapi::GitHub::Request request(std::move(connection));
qInfo() << request.getRateLimit().c_str();
qInfo() << request.getUserInfo("Kicer86").c_str();
return 0;
}Here the connection is built with ConnectionBuilder.
The builder sets the GitHub API URL and headers automatically.
When calling build(factory, args...), any backend-specific arguments are forwarded to the factory before the URL and headers.
For example, the Qt factory receives the QNetworkAccessManager first.
The returned std::unique_ptr<IConnection> is then moved into GitHub::Request, which owns the connection.
Refer to the documentation of ConnectionBuilder for more details.
The cpp_restapi::GitHub::Request class provides accessors for the most common GitHub API requests.
#include <iostream>
#include <utility>
#include <cpp_restapi/create_curl_connection.hpp>
#include <cpp_restapi/github/connection_builder.hpp>
#include <cpp_restapi/github/request.hpp>
int main(int argc, char** argv)
{
auto connection = cpp_restapi::GitHub::ConnectionBuilder().build(cpp_restapi::createCurlConnection);
cpp_restapi::GitHub::Request request(std::move(connection));
std::cout << request.getRateLimit() << '\n';
std::cout << request.getUserInfo("Kicer86") << '\n';
return 0;
}#include <iostream>
#include <utility>
#include <cpp_restapi/create_cpp-httplib_connection.hpp>
#include <cpp_restapi/github/connection_builder.hpp>
#include <cpp_restapi/github/request.hpp>
int main(int argc, char** argv)
{
auto connection = cpp_restapi::GitHub::ConnectionBuilder().build(cpp_restapi::createCppHttplibConnection);
cpp_restapi::GitHub::Request request(std::move(connection));
std::cout << request.getRateLimit() << '\n';
std::cout << request.getUserInfo("Kicer86") << '\n';
return 0;
}See the examples directory for complete example programs.
In addition to regular REST requests, the library supports Server-Sent Events — a standard mechanism for receiving a stream of events from a server over HTTP.
SSE support is available for all three backends via IConnection::subscribe().
The method connects to an SSE endpoint,
delivers parsed events through a callback and returns an ISseConnection handle.
The call is non-blocking — events are received on an internal
thread (or via the Qt event loop for the Qt backend). Use close() to stop.
Keep the returned ISseConnection alive for as long as you want to receive events.
For the Qt backend, the Qt event loop must be running.
#include <iostream>
#include <thread>
#include <chrono>
#include <cpp_restapi/create_curl_connection.hpp>
#include <cpp_restapi/iconnection.hpp>
#include <cpp_restapi/isse_connection.hpp>
#include <cpp_restapi/sse_event.hpp>
int main(int argc, char** argv)
{
auto connection = cpp_restapi::createCurlConnection("http://localhost:8080", {});
auto sse = connection->subscribe("events", [](const cpp_restapi::SseEvent& event)
{
std::cout << "Event: " << event.event << '\n';
std::cout << "Data: " << event.data << '\n';
});
// Do other work while events arrive in the background
std::this_thread::sleep_for(std::chrono::seconds(30));
sse->close();
return 0;
}#include <iostream>
#include <QCoreApplication>
#include <QNetworkAccessManager>
#include <cpp_restapi/create_qt_connection.hpp>
#include <cpp_restapi/iconnection.hpp>
#include <cpp_restapi/isse_connection.hpp>
#include <cpp_restapi/sse_event.hpp>
int main(int argc, char** argv)
{
QCoreApplication qapp(argc, argv);
QNetworkAccessManager manager;
auto connection = cpp_restapi::createQtConnection(manager, "http://localhost:8080", {});
auto sse = connection->subscribe("events", [](const cpp_restapi::SseEvent& event)
{
std::cout << "Event: " << event.event << '\n';
std::cout << "Data: " << event.data << '\n';
});
return qapp.exec();
}#include <iostream>
#include <thread>
#include <chrono>
#include <cpp_restapi/create_cpp-httplib_connection.hpp>
#include <cpp_restapi/iconnection.hpp>
#include <cpp_restapi/isse_connection.hpp>
#include <cpp_restapi/sse_event.hpp>
int main(int argc, char** argv)
{
auto connection = cpp_restapi::createCppHttplibConnection("http://localhost:8080", {});
auto sse = connection->subscribe("events", [](const cpp_restapi::SseEvent& event)
{
std::cout << "Event: " << event.event << '\n';
std::cout << "Data: " << event.data << '\n';
});
// Do other work while events arrive in the background
std::this_thread::sleep_for(std::chrono::seconds(30));
sse->close();
return 0;
}The SseEvent struct exposes all standard SSE fields:
| Field | Type | Description |
|---|---|---|
event |
std::string |
Event type (from event: field, empty if not specified) |
data |
std::string |
Event payload (from data: field(s), joined with \n) |
id |
std::string |
Last event ID (from id: field) |
retry |
int |
Reconnection time in ms (from retry: field, -1 if N/A) |
All three backends support non-blocking HTTP requests via a callback-based API.
fetch() returns immediately and delivers the result through onSuccess / onError
callbacks. It also returns a CancellationToken that can be used to suppress callbacks
before they fire.
For non-Qt backends (curl, cpp-httplib) callbacks run on a background std::thread.
For the Qt backend, callbacks are invoked on the Qt event-loop thread.
#include <iostream>
#include <future>
#include <cpp_restapi/create_curl_connection.hpp>
#include <cpp_restapi/iconnection.hpp>
int main()
{
auto connection = cpp_restapi::createCurlConnection("https://swapi.dev/api", {});
std::promise<void> done;
auto future = done.get_future();
auto cancel = connection->fetch("people/1",
[&done](cpp_restapi::Response resp)
{
std::cout << "Status: " << resp.statusCode << '\n';
std::cout << "Body: " << resp.body << '\n';
done.set_value();
},
[&done](cpp_restapi::HttpError err)
{
std::cerr << "Error " << err.statusCode << ": " << err.message << '\n';
done.set_value();
});
// Do other work while the request runs in the background...
std::cout << "Request in flight — doing other work...\n";
future.wait();
return 0;
}#include <iostream>
#include <QCoreApplication>
#include <QNetworkAccessManager>
#include <cpp_restapi/create_qt_connection.hpp>
#include <cpp_restapi/iconnection.hpp>
int main(int argc, char** argv)
{
QCoreApplication qapp(argc, argv);
QNetworkAccessManager manager;
auto connection = cpp_restapi::createQtConnection(manager, "https://swapi.dev/api", {});
auto cancel = connection->fetch("people/1",
[&qapp](cpp_restapi::Response resp)
{
std::cout << "Status: " << resp.statusCode << '\n';
std::cout << "Body: " << resp.body << '\n';
qapp.quit();
},
[&qapp](cpp_restapi::HttpError err)
{
std::cerr << "Error " << err.statusCode << ": " << err.message << '\n';
qapp.quit();
});
return qapp.exec();
}The CancellationToken returned by async fetch() is a std::shared_ptr<std::atomic<bool>>.
Setting it to true suppresses further callbacks. It does not guarantee that an already-started
network request is aborted immediately:
auto cancel = connection->fetch("slow/endpoint",
[](cpp_restapi::Response) { /* ... */ },
[](cpp_restapi::HttpError) { /* ... */ });
// Changed our mind — suppress callbacks
cancel->store(true);Paginated requests can also be performed asynchronously.
The merged result is delivered through a BodyCallback once all pages have been collected:
cpp_restapi::LinkHeaderPaginationStrategy strategy;
auto cancel = connection->fetch("repos/owner/repo/issues", strategy,
[](std::string mergedBody)
{
std::cout << "All pages: " << mergedBody << '\n';
},
[](cpp_restapi::HttpError err)
{
std::cerr << "Error on page: " << err.statusCode << '\n';
});The header-only <cpp_restapi/coroutine.hpp> provides lightweight coroutine
wrappers around the callback-based async API. The project requires C++23, and
the coroutine helpers also require compiler support for <coroutine>.
| Type / Function | Description |
|---|---|
Detached |
Fire-and-forget wrapper — starts a coroutine that runs to completion on its own. |
coFetch(conn, request) |
Returns an awaitable yielding std::expected<Response, HttpError>. |
coFetch(conn, request, strategy) |
Returns an awaitable yielding std::expected<std::string, HttpError> (paginated). |
#include <iostream>
#include <future>
#include <cpp_restapi/coroutine.hpp>
#include <cpp_restapi/create_curl_connection.hpp>
int main()
{
auto connection = cpp_restapi::createCurlConnection("https://swapi.dev/api", {});
std::promise<void> done;
auto future = done.get_future();
[&]() -> cpp_restapi::Detached
{
auto result = co_await cpp_restapi::coFetch(*connection, "people/1");
if (result)
std::cout << "Status: " << result->statusCode << '\n'
<< "Body: " << result->body << '\n';
else
std::cerr << "Error: " << result.error().message << '\n';
done.set_value();
}();
future.wait();
return 0;
}Standalone builds are mostly useful when you want to run the examples or unit tests from this repository. The main integration scenario is still to include cpp_restapi as a CMake subproject.
It is possible to build this project as any other regular CMake project by invoking:
cmake -B build -DCppRestAPI_CurlBackend=ON
cmake --build buildReplace CppRestAPI_CurlBackend with another backend option if you prefer Qt or cpp-httplib.
At least one backend option must be enabled, otherwise configuration fails.
This repository has an optional vcpkg submodule. It is not required when you include cpp_restapi as a subproject,
but it can simplify standalone builds by providing dependencies.
Please mind that vcpkg uses telemetry.
Visit https://learn.microsoft.com/vcpkg/about/privacy for more details.
If you want to use the bundled vcpkg checkout, initialize it and configure CMake with the vcpkg toolchain file:
git submodule update --init vcpkg
./vcpkg/bootstrap-vcpkg.sh
cmake -B build -DCMAKE_TOOLCHAIN_FILE=$PWD/vcpkg/scripts/buildsystems/vcpkg.cmake -DCppRestAPI_CppHttplibBackend=ONThe repository also provides vcpkg-based CMake presets in CMakePresets.json.
Examples are located in the examples directory of the project.
To build them set CppRestAPI_Examples CMake variable to ON.
It can be done when invoking cmake command by providing the -DCppRestAPI_Examples=ON command-line argument (see the Basic standalone build section),
or by modifying the CppRestAPI_Examples entry in the CMakeCache.txt file located in the build directory of an already configured project.
Please mind that setting CppRestAPI_Examples to ON forces all backends and optional components to be used, so all backend dependencies must be available.
Unit tests are located in the tests directory of the project.
To build them set CppRestAPI_Tests CMake variable to ON.
Please mind that setting CppRestAPI_Tests to ON forces all backends and optional components to be used, so all backend dependencies must be available.
Code documentation available at https://kicer86.github.io/cpp_restapi/index.html