C++ RESTful web service with Swagger-UI and auto-documented endpoints

This is a 5 minutes tutorial how-to-use Oat++ web framework to build your own C++ performant web-service and integrate it with Swagger-UI.

Pre Requirements

  • For Mac/Linux: Git, build-essentials, CMake.
  • For Windows: Git, CMake, MSVC.

Install Oat++

Clone oatpp repo:

$ git clone https://github.com/oatpp/oatpp
$ cd oatpp/

On Mac/Linux:

$ mkdir build && cd build/
$ cmake ..
$ make install

For more detailed instructions see - installing Oat++ on Unix/Linux.

On Windows:

$ MD build
$ cd build/
$ cmake ..
$ cmake --build . --target INSTALL

For more detailed instructions see - installing Oat++ on Windows.

Now we are able to build services using Oat++. It is a zero-dependency framework, so NO additional installations are required.


Build and Run Starter Project

Clone “oatpp-starter”

$ git clone --depth=1 https://github.com/oatpp/oatpp-starter my-service
$ cd my-service/

Build “oatpp-starter” on Mac/Linux

$ mkdir build && cd build/
$ cmake ..
$ make

Build “oatpp-starter” on Windows

$ MD build
$ cd build/
$ cmake ..
$ cmake --build .

Run compiled executable:

  • Mac/Linux - $ ./my-project-exe
  • Windows - $ .\src\Debug\my-project-exe.exe

Now go to localhost:8000 _and m_ake sure that you can see the response from server

{"statusCode": 200, "message": "Hello World!"}

Project Structure

- src/
   |
   |- controller/              
   |   |
   |   |- MyController.hpp  // Endpoints are declared here
   |
   |- dto/
   |   |
   |   |- DTOs.hpp          // DTOs are here
   |
   |- App.cpp               // main is here
   |- AppComponent.hpp      // Application components configuration

MyController class - contains declared endpoints and their info together with additional Swagger annotations.

MyDto class - resides in DTOs.hpp. Describes the Data-Transfer-Object used in the "Hello World" response mentioned above. In oatpp DTOs are used in ObjectMapping and Serialization/Deserialization.

App.cpp file - this is an applications' entry point. Here Application Components are loaded, Controllers' endpoints are added to the Router, and the server starts.

AppComponent class - basically it is a collection of components that will be loaded on application start. Here we configure things like which ConnectionProvider to use, port to listen to, which ObjectMapper to use.

Integrate Swagger-UI

To integrate Swagger-UI in the project we have to do the following:

  • Clone and install oatpp-swagger module
  • Add oatpp-swagger to CMakeLists.txt
  • Add corresponding code to AppComponent.hpp and App.cpp

Install oatpp-swagger

$ git clone https://github.com/oatpp/oatpp-swagger
$ cd oatpp-swagger/

Linux/Mac:

$ mkdir build && cd build/
$ cmake ..
$ make install

Windows:

$ MD build
$ cd build/
$ cmake ..
$ cmake --build . --target INSTALL

Add oatpp-swagger to CMakeLists.txt

...

## add libs
find_package(oatpp          1.1.0 REQUIRED)
find_package(oatpp-swagger  1.1.0 REQUIRED) # <-- add this

target_link_libraries(${project_name}-lib
        PUBLIC oatpp::oatpp
        PUBLIC oatpp::oatpp-swagger         # <-- add this
)

## define path to swagger-ui res folder
add_definitions( 
  -DOATPP_SWAGGER_RES_PATH="${OATPP_BASE_DIR}/bin/oatpp-swagger/res"
) # <-- add this

...

AppComponent.hpp

Here we add oatpp::swagger::DocumentInfo and oatpp::swagger::Resources components which give general information about our API document and specify a path to Swagger-UI resources:

#include "oatpp-swagger/Model.hpp"
#include "oatpp-swagger/Resources.hpp"

...

class AppComponent {

...

  /**
   *  General API docs info
   */
  OATPP_CREATE_COMPONENT(
    std::shared_ptr<oatpp::swagger::DocumentInfo>, 
    swaggerDocumentInfo
  )([] {
    
    oatpp::swagger::DocumentInfo::Builder builder;
    builder
     .setTitle("My Demo Service with Swagger-UI")
     .setDescription("C++/oat++ Web Service with Swagger-UI")
     .setVersion("1.0")
     .setContactName("Mr. Developer")
     .setContactUrl("https://oatpp.io/")
     .setLicenseName("Apache License, Version 2.0")
     .setLicenseUrl("http://www.apache.org/licenses/LICENSE-2.0")
     .addServer("http://localhost:8000", "server on localhost");
     return builder.build();
  }());

  /**
   *  Swagger-Ui Resources
   */
  OATPP_CREATE_COMPONENT(
    std::shared_ptr<oatpp::swagger::Resources>, 
    swaggerResources
  )([] {
    return oatpp::swagger::Resources::loadResources(
      OATPP_SWAGGER_RES_PATH
    );
  }());

};

App.cpp

Here we add oatpp::swagger::Controller to Router with the list of endpoints we want to document

#include "oatpp-swagger/Controller.hpp"

...

void run() {
  
  ...

  auto docEndpoints =
    oatpp::swagger::Controller::Endpoints::createShared();

  docEndpoints->pushBackAll(myController->getEndpoints());

  auto swaggerController =
    oatpp::swagger::Controller::createShared(docEndpoints);
    
  swaggerController->addEndpointsToRouter(router);

  ...

}

Now if everything is ok, and OATPP_SWAGGER_RES_PATH path is set correctly in the AppComponent.hpp, we should be able to build and run our project and see Swagger-UI at http://localhost:8000/swagger/ui in the browser

Additional Info for Endpoint

Our endpoint is already present in the document with the proper method and path. Oat++ automatically documents most of the endpoints’ info, such as endpoint name, method, path, parameter names, and parameter types. However, there are things that should be specified explicitly.

Annotate endpoint with additional information

In the file **MyController.hpp **we add ENDPOINT_INFO above the root ENDPOINT with summary and response information:

ENDPOINT_INFO(root) {
  info->summary = "Root endpoint with 'Hello World!!!' message";
  info->addResponse<Object<MyDto>>(
    Status::CODE_200, 
    "application/json"
  );
}
ENDPOINT("GET", "/", root) {
  auto dto = MyDto::createShared();
  dto->statusCode = 200;
  dto->message = "Hello World!";
  return createDtoResponse(Status::CODE_200, dto);
}

Build, Run, and go to http://localhost:8000/swagger/ui in the browser. Refresh

Notice, that summary is added to the endpoint and MyDto schema automatically documented in the Models.

Expand endpoint info and check that response is documented correctly


Basically that’s it 🎉

Now we have Swagger-UI integrated into our project and we can easily add and document endpoints!


Add endpoint

Let’s add one more "echo" endpoint and see how it is documented in the swagger.

In the file MyController.hpp:

ENDPOINT_INFO(echo) {
  info->summary = "Echo endpoint with custom message";
  info->addResponse<Object<MyDto>>(
    Status::CODE_200,
    "application/json"
  );
}
ENDPOINT("POST", "/echo/status/{status}", echo,
         PATH(Int32, status), BODY_STRING(String, message)) {
  auto dto = MyDto::createShared();
  dto->statusCode = status;
  dto->message = message;
  return createDtoResponse(Status::CODE_200, dto);
}

Build and run… Refresh…

Expand echo endpoint info - you can see the "status" and "Request Body" parameters:

Put in some values:

Execute the request and verify that you have a correct response from the server:


That’s it. You may experiment by adding more endpoints, playing with parameters, and DTO-fields to see how it is being documented in Swagger-UI.



Soumyajit Pathak picture

This looks great. And as a node.js user primarily. It makes me think of NestJS. They look quite similar at a quick glance.

Leonid Stryzhevskyi picture

Hey,

Thanks for the feedback!

Indeed they look pretty similar, However, the most inspiration for Oat++ came from java-spring. Please find more info in the reply to the previous comment by @RhettTrickett

Soumyajit Pathak picture

I can only imagine Nestjs was inspired by java-spring as well. Java Spring is such a big name in the web services. The comment below helps.

Thanks for taking the time to make this and post about it. Will love to get know more about Oat++

Rhett Trickett picture

Oat++ looks really impressive, Leonid. I'm keen to give this a try with a new project idea sometime.

What prompted you to start the project? Was the goal to build a web framework for maximum efficiency or is it intended to serve certain types of projects/applications? And what was the reason behind choosing to go with no dependencies?

Thanks for sharing. Would be interesting to read about the reasoning behind development decisions, structure, stuff you're working on at the moment etc. Projects like this are great.

Leonid Stryzhevskyi picture

Hey,

Thanks for the feedback!

The main reason to start the project was the enormous cost of today's web infrastructure and the resource inefficiency of industry-standard solutions like java-spring.

Java-spring is an industry standard for a reason - it has simple API, it's feature-rich, and it has developed infrastructure around it.

Oat++ was inspired by Spring and it might be the best way to describe what Oat++ is by saying that it's Spring for C++. The difference is that Oat++ is resource-efficient, high-performance, and it can run on embedded devices - see https://oatpp.io/supported-platforms/. So in many ways, Oat++ is targeting the IoT development by providing a comprehensive set of tools for web development both on embedded devices and for serving a million connections in the cloud.

Zero-dependency - you can port Oat++ almost wherever you want, and you don't need to mess with 3rd parties that come with it.

Hope I answered most of the questions.

Regards, Leonid

Rhett Trickett picture

Very cool. It's seriously impressive that you're building all of this from scratch. It must be an excellent learning experience.

I've literally just started an IoT project this week and chose Express to run an API on a Raspberry Pi. The plan is to then build a temperature/humidy sensor with an ESP32 (written in C) and post readings to the API. I want to start doing a lot more embedded/web API stuff though so will keep an eye out for an opportunity to try out Oat++.

Nice work, Leonid. Keep going!