How to create a REST API with QtHttpServer

Current status of QtHttpServer

Back in 2019 we announced QtHttpServer to the public, now we are extremely happy to introduce a technical preview,
starting in Qt 6.4. for this project.

What is QtHttpServer

Qt Http Server is a high-level API that provides easy support for implementing an HTTP server in your application.
It is designed to be embedded in applications to expose things on a trusted network and does not have robustness/security as a goal, it is not suitable for use on the public internet.

How does the REST API work

The REST API communicates with the server using HTTP. The REST API is used to perform CRUD - create, read, update, delete - operations on a given resource. This uses a variety of HTTP methods (e.g. GET for reading operations, POST for creating operations, PUT and PATCH for updating resources, and DELETE for deleting data). Data is usually transmitted via HTTP request/response bodies in the form of JSON objects.

Implementing a REST API

For the sake of simplicity, we will discuss only a few of the most important parts like routing, handling requests, creating responses and basic authorization. Fully functional examples can be found in the documentation.

Hello world

#include <QtCore>
#include <QtHttpServer>

int main(int argc, char **argv)
{
    QCoreApplication app(argc, argv);
    QHttpServer httpServer;

    const auto port = httpServer.listen(QHostAddress::Any);

    if (!port)
        return 0;

    qDebug() << QCoreApplication::translate("QHttpServerExample",
            "Running on http://127.0.0.1:%1/ (Press CTRL+C to quit)").arg(port);
    return app.exec(); 
} 

Let's start with the backbone of our server. We create a QCoreApplication with QHttpServer running on our local address. We leave QHttpServer to choose a port. Next, we need to make the server route specific requests to suitable handlers.

Define routes

GET route

httpServer.route("/myApi", QHttpServerRequest::Method::Get,
    [&myData](const QHttpServerRequest &request) {
        QJsonArray array = myData;
        return QHttpServerResponse(array);
    }
);
Starting with the GET method, our API in this case returns a list of results as a JSON array. To achieve that, we need to call QHttpServer::route() with a path that will match a request, the method (or methods) that are supported for that resource (if not specified, then all HTTP methods will be matched) and the last argument is a handler which shall be called. In this example, we route GET requests for /myApi to a handler that produces a response. The handler returns QJsonArray as a QHttpServerResponse with (default) HTTP status code 200 - OK.

GET route with dynamic parameters

httpServer.route("/myApi/<arg>", QHttpServerRequest::Method::Get,
    [&myData](int id, const QHttpServerRequest &request) {
        QJsonObject myApiObject = myData.find(id);
        return QHttpServerResponse(myApiObject);
    }
);
Now, let's add a route to return only one item instead of the whole list. To make it possible, we define a dynamic route that matches an int argument after the /myApi/ path. We define that by including a placeholder, <arg>, in the path. Because our handler takes an int parameter, this route is used when the relevant part of the URL can be converted to an int, which will be passed to the handler.
In this example, the <arg> placeholder could even be omitted because it appears at the end of the path expression.
 
To know more about routing, check out this blog post.

POST route with authorization

bool authorize(const QHttpServerRequest &request)
{
    for (const auto &[key, value] : request.headers()) {
        if (key == “SECRET_KEY” && value == “SECRET_VALUE”)
            return true;
    }
    return false;
}

QJsonObject byteArrayToJsonObject(const QByteArray &arr)
{
    const auto json = QJsonDocument::fromJson(arr);
    return json.object();
}

httpServer.route("/myApi", QHttpServerRequest::Method::Post,
    [&myData](const QHttpServerRequest &request) {
        if (!authorize(request))
            return QHttpServerResponse(QHttpServerResponder::StatusCode::Unauthorized);

        const QJsonObject newObject = byteArrayToJsonObject(request.body());
        myData.insert(newObject);
        return QHttpServerResponse(newObject, QHttpServerResponder::StatusCode::Created);
    }
);
Let's take a look at the POST method snippet. First, we want to authorize the modification request. We scan the request headers to find the SECRET_KEY header and its value equal to the string SECRET_VALUE (obviously, you would do something more secure in reality). If that header is not found, we return QHttpServerResponse with HTTP status code 401 - Unauthorized. Then we extract the JSON object from the request body and store it in myData. Finally, we return an HTTP response containing the same JSON object along with HTTP status code 201 – Created.

Hosting files

httpServer.route("/myApi/img/<arg>-image.jpg", QHttpServerRequest::Method::Get,
    [](int imageId, const QHttpServerRequest &) {
        return QHttpServerResponse::fromFile(QString(":/assets/img/%1-image.jpg")
                .arg(imageId));
    }
);
The last thing that we wanted to show is the easy hosting of files for our API. To return a static file as an HTTP response, we can use the convenience method QHttpServerResponse::fromFile().

This shows how to do simple things - for more detailed and full examples, please check out our examples page or QtHttpServer documentation.

Keep in mind that this project is in the technical preview phase, which means that we are still working on it,
and some things are subject to change. Therefore, your feedback is extremely important.
We hope that you will try our new project and share your opinions with us.

Blog Topics:

Comments