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

Commenting for this post has ended.

Erwin Kandler
2 points
32 months ago

Looks promising! Now add LGPL as possible license for this module please!

Felix
1 point
31 months ago

Could you explain why exactly it is not suitable for the public internet?

M
Mårten Nordheim
0 points
31 months ago

It was simply not something that was part of the scope of the project, so while we can be relatively sure it will behave well in regular use, we don't have any guarantees about exposing it to an untrusted environment.

H
Hamish Moffatt
0 points
31 months ago

Because writing a secure HTTP server is hard. Put a proxy server in front of it like nginx or Apache.

Felix
0 points
31 months ago

Thanks for the tip, I like that idea. Although that still wouldn't cover all the vulnerabilities

V
Vladimir Minenko
0 points
31 months ago

I think "no suitable" should be rather understood as "not intended to be used for". We want to provide the possibility to add HTTP-server functionality in Qt apps. One use case for this, as mentioned in this blog, is implementing a REST end-point. In this, we are not going to provide another nginx. But by the nature of things, it is had to put limits or implement a border. So we appeal to users to make sure they are aware of the difference, use it accordingly and so not expect something which will never come in the future

P-A Giguère
1 point
31 months ago

We have been using the QtHttpServer since 2020 in production and absolutely love it! It greatly simplified our networking stack and got easy threading at the same time. Thanks to all the team!

V
Vladimir Minenko
0 points
31 months ago

Thank you! Glad to hear! I would be interested to learn how it is used and what you would need in the future. Feel free to contact me via email: my first name, dot, my last name at qt io. I'm the PM for this domain.

Silver Zachara
0 points
31 months ago

I like the API, nice

J
Jack
0 points
31 months ago

LGPL please!

AGL-Unix
0 points
31 months ago

LGPL please!

Sergio Andrade
0 points
15 months ago

In this example: 127.0.0.1:8001/index.html | it works, but if I do: 127.0.0.1:8001/icons/icon.png | it does not work. Could you help me? httpServer.route("/myApi/", QHttpServerRequest::Method::Get, [](QString filename, const QHttpServerRequest &) { return QHttpServerResponse::fromFile(QString("%1") .arg(filename)); } );

Sergio Andrade
0 points
15 months ago

Now I understood how it is done.httpServer.route("/assets/<>", [] (const QUrl &url) { return QHttpServerResponse::fromFile(u":/assets/"_s + url.path());}); Just pass the args as QUrl, then all "subfolders" will work too.

Alex
0 points
14 months ago

Both links " examples page" and "QtHttpServer documentation" are 404.

D
damiane
0 points
3 months ago

Creating a REST API with QtHttpServer is pretty straightforward, especially with its high-level API. You can easily handle routes, requests, and responses, and even add basic auth. For hosting, if you need a secure and anonymous setup, read this article. They’re great for keeping things private while running your server. QtHttpServer is still in preview, so feedback is key—give it a spin!