“Why the fuck would I write a backend service in C++ and not glorious JavaScript?”

Because we can. And we will.

Table of Contents

  1. Why C++ for Backend Services
  2. What Makes µWebSockets Legendary
  3. Part 1: The Build That Works
  4. Part 2: The Code
  5. Step 5: Testing Your WebSocket Server
  6. Lessons Learned (aka: Things That Made Me Question My Life Choices)
  7. Wrapping Up

Why C++ for Backend Services?

Again, Because we fucking can. And we fucking will.

We’re not building another Todo app. We’re not “moving JSON from one box to another.” We’re building systems that handle millions of concurrent connections, where every nanosecond and every byte of RAM counts. We’re building the shit that financial exchanges, AAA game servers, and high-frequency trading bots run on.

When you need that kind of raw, unadulterated, “melt-your-CPU-in-a-good-way” performance, you don’t reach for a tool built for animating browser buttons.

µWebSockets isn’t just fast - it’s so fast it does encrypted TLS 1.3 messaging quicker than most servers can handle unencrypted traffic.
Read that again.

What Makes µWebSockets Legendary?

This isn’t your typical “look ma, I made a server” library. µWebSockets powers some of the biggest crypto exchanges in the world, handling billions of USD in trades every single day. When you trade crypto, there’s a good chance you’re doing it through µWebSockets and don’t even know it.

  • Battle-tested since 2016 - Perfect Autobahn|Testsuite compliance
  • 95% daily fuzzing coverage - Google’s OSS-Fuzz, zero sanitizer issues
  • LGTM score: A+ - Zero CodeQL alerts
  • Trusted by the big boys - BitMEX, Bitfinex, Coinbase, and the exchanges that move billions daily

Part 1: The Build that Works

Installing µWebSockets as a git submodule

This is not Ja*aScript, it’s c++ have to build your own stuff. This is what separates us from the js andies.

First, we get our dependencies. uWebSockets as a git submodule.

[!NOTE]
you need to have a git repo initialized for this.

This might take a while depending on your internet connection.

# Add µWebSockets as a submodule
git submodule add https://github.com/uNetworking/uWebSockets.git external/uwebsockets
git submodule update --init --recursive

CMake Setup: The Part Everyone Fucks Up

You Thought You Could Just ‘cmake .’? That’s Fucking Adorable.
Your dependencies don’t give a shit about your feelings. They don’t use your build system.

The Problem Most Tutorials Don’t Tell You
µWebSockets depends on uSockets - the underlying I/O layer that makes everything fast. uSockets has its own Makefile, and it works perfectly.

The Secret Sauce: Let uSockets Build Itself

cmake_minimum_required(VERSION 3.20)
project(WebSocketServer CXX)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(TARGET_NAME websocket_server)

# ============================================
# Find System Dependencies
# ============================================
find_package(OpenSSL REQUIRED)
find_package(ZLIB REQUIRED)

# ============================================
# Build uSockets (The Secret Sauce)
# ============================================
set(USOCKETS_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/uwebsockets/uSockets)
set(USOCKETS_LIBRARY ${USOCKETS_SOURCE_DIR}/uSockets.a)

# Build uSockets using its own Makefile with OpenSSL support
add_custom_command(
    OUTPUT ${USOCKETS_LIBRARY}
    COMMAND ${CMAKE_MAKE_PROGRAM} WITH_OPENSSL=1 -C ${USOCKETS_SOURCE_DIR}
    COMMENT "Building uSockets library with OpenSSL support"
)

add_custom_target(build_usockets ALL DEPENDS ${USOCKETS_LIBRARY})

# Import uSockets as a CMake library
add_library(uSockets STATIC IMPORTED)
set_target_properties(uSockets PROPERTIES
    IMPORTED_LOCATION "${USOCKETS_LIBRARY}"
    INTERFACE_INCLUDE_DIRECTORIES "${USOCKETS_SOURCE_DIR}/src"
)
add_dependencies(uSockets build_usockets)

# ============================================
# Create µWebSockets Interface Library
# ============================================
add_library(uWebSockets_Interface INTERFACE)

target_include_directories(uWebSockets_Interface INTERFACE
    ${CMAKE_CURRENT_SOURCE_DIR}
)

target_link_libraries(uWebSockets_Interface INTERFACE
    uSockets
    ZLIB::ZLIB
    OpenSSL::SSL
    OpenSSL::Crypto
)

# ============================================
# Build Your Application
# ============================================
file(GLOB TARGET_SOURCES "src/*.cpp")
list(FILTER TARGET_SOURCES EXCLUDE REGEX ".*/main\\.cpp$")

add_library(app_lib STATIC ${TARGET_SOURCES})
target_link_libraries(app_lib PUBLIC uWebSockets_Interface)
target_include_directories(app_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
add_dependencies(app_lib build_usockets)

add_executable(${TARGET_NAME} src/main.cpp)
target_link_libraries(${TARGET_NAME} PRIVATE app_lib)

# Compiler warnings (because we're professionals)
if(MSVC)
    target_compile_options(${TARGET_NAME} PRIVATE /W4 /WX)
else()
    target_compile_options(${TARGET_NAME} PRIVATE -Wall -Wextra -Wpedantic)
endif()

Building the Library

BEFORE YOU RUN THIS BUILD COMMAND YOU NEED TO HAVE A src/main.cpp AND src/WebSocketServer.cpp FILE

The src/WebSocketServer.cpp file can even be empty. And you can have a minimal program like this in your src/main.cpp:

int main() {
    return 0;
}

Don’t worry, we’ll write the actual code later. This is just to test that CMake works.

Note: Not sure if this shit will work on Windows due to those glob patterns in CMake.
If you’re on Windows and hit issues, you might need to explicitly list your source files
instead of using file(GLOB ...). But that’s a Windows problem, not a you problem.

Create the basic structure:

mkdir -p include src certs
touch src/main.cpp src/WebSocketServer.cpp

Your directory structure should look something like this now:

.
├── certs
├── CMakeLists.txt
├── external
│   └── uwebsockets        # created by git submodule
├── include
└── src
    ├── main.cpp
    └── WebSocketServer.cpp

6 directories, 3 files

Run this in the root of your project:

cmake -S . -B build
cd build && make

At this point, you might be thinking “great, it works!” and you’d be right. It does work. It works beautifully.
Until you try to add SSL.


SSL Certificates: Because Security Matters

We’re using wss:// (Secure WebSockets), not that unencrypted peasant shit. For local development, self-signed certificates work fine.

# Generate self-signed certificate (valid for 365 days)
openssl req -x509 -sha256 -newkey rsa:2048 \
    -keyout certs/key.pem \
    -out certs/cert.pem \
    -days 365 -nodes \
    -subj "/C=IN/O=MY ORG./CN=localhost" \
    -addext "subjectAltName = DNS:localhost"

Part 2: The Code

Now that we’ve got our build system unfucked, it’s time to write the actual server. We’re keeping this clean, and simple.

NOTE: we’re not going to write a 5000 lines code, we’ll be keeping things fairly straight forward to understand.

  • A WebSocket server class (one header, one implementation)
  • Event handlers that make sense
  • A main function that just works
  • That’s it. No bullshit.

Step 1: The Header File

include/WebSocketServer.hpp

#pragma once
#include "external/uwebsockets/src/App.h"
#include <string>
#include <string_view>

class WebSocketServer {
  public:
    explicit WebSocketServer(int port, const std::string &key_file,
                             const std::string &cert_file);

    // Run the server (this blocks until shutdown)
    void run();

  private:
    // Per-connection data - store whatever you need here
    struct SocketData {
        std::string user_id;
        uint64_t connected_at;
    };

    // Type aliases to keep our sanity
    constexpr static bool IS_USING_SSL = true;
    constexpr static bool IS_SERVER = true;
    using WebSocket = uWS::WebSocket<IS_USING_SSL, IS_SERVER, SocketData>;

    // The lifecycle callbacks
    void setup_routes();
    void on_listen(us_listen_socket_t *listen_socket);
    void on_open(WebSocket *ws);
    void on_message(WebSocket *ws, std::string_view message,
                    uWS::OpCode op_code);
    void on_close(WebSocket *ws, int code, std::string_view message);

    // Member variables
    std::string key_file_;
    std::string cert_file_;
    uWS::SSLApp app_;
    int port_;
};

This is our main server class. We’re wrapping all the µWebSockets logic in a proper C++ class obv we don’t need all this crap
just to get our socket server running, but this is just sorta a good practice just to organize our code and all.

What the hell is this?

  • SocketData struct: This is the most important part. µWebSockets is fast because it’s stateless.
    If you want to associate data with a connection (like a user ID, auth token, etc.), you define it in this struct.
    The library will then allocate this struct per-connection for you.
  • Type aliases (using WebSocket = …): The uWS::WebSocket template is a monstrosity.
  • Callbacks (on_open, on_message, on_close): These are our class methods that
    will handle the WebSocket event lifecycle. We’ll wire them up in the .cpp file.
  • uWS::SSLApp app_;: This is the actual µWebSockets server object. We initialize it with our SSL certs.

Step 2: The Implementation

src/WebSocketServer.cpp

#include "include/WebSocketServer.hpp"
#include <iostream>
#include <string>
#include <string_view>

WebSocketServer::WebSocketServer(int port, const std::string &key_file,
                                 const std::string &cert_file)
    : key_file_(key_file), cert_file_(cert_file),
      app_({
          .key_file_name = key_file_.c_str(),
          .cert_file_name = cert_file_.c_str(),
      }),
      port_(port) {
    setup_routes();
}

void WebSocketServer::setup_routes() {
    // We might've just made the on_* function static in the header and could've
    // done {.open = on_open(ws);} but this would later create pain in our ass
    // when we try to access some non static member variables from our class.
    app_.ws<SocketData>(
        "/*",
        {.open = [this](auto *ws) { this->on_open(ws); },
         .message = [this](auto *ws, std::string_view msg,
                           uWS::OpCode op) { this->on_message(ws, msg, op); },
         .close =
             [this](auto *ws, int code, std::string_view msg) {
                 this->on_close(ws, code, msg);
             }});

    app_.get("/health", [](auto *res, auto *req) {
        res->writeStatus("200 OK")
            ->writeHeader("Content-Type", "application/json")
            ->end(R"({"status":"healthy"})");
    });
}

void WebSocketServer::on_open(WebSocket *ws) {
    auto *data = ws->getUserData();

    data->user_id = std::to_string(reinterpret_cast<uintptr_t>(ws));
    data->connected_at =
        std::chrono::system_clock::now().time_since_epoch().count();

    std::cout << "[+] Connection from " << data->user_id << "\n";

    ws->send(R"({"event":"connected","message":"Welcome!"})",
             uWS::OpCode::TEXT);
}

void WebSocketServer::on_message(WebSocket *ws, std::string_view message,
                                 uWS::OpCode op_code) {
    auto *data = ws->getUserData();

    std::cout << "[->] Message from " << data->user_id << ": " << message
              << "\n";

    // Echo it back (you'll want to do something smarter here)
    ws->send(message, op_code);
}

void WebSocketServer::on_close(WebSocket *ws, int code,
                               std::string_view message) {
    auto *data = ws->getUserData();

    std::cout << "[-] " << data->user_id << " disconnected\n";
}

void WebSocketServer::run() {
    app_.listen(port_,
                [this](auto *socket) {
                    if (socket) {
                        std::cout << "\nWebSocket server listening on port "
                                  << port_ << "\n";
                        std::cout << "WSS endpoint: wss://localhost:" << port_
                                  << "\n";
                        std::cout << "Health check: https://localhost:" << port_
                                  << "/health\n\n";
                    } else {
                        std::cerr
                            << "webSocket server failed to listen on port "
                            << port_ << std::endl;
                        return;
                    }
                })
        .run();
}

Breaking It Down

The constructor and run() method are the main entry points since they’re called directly from main.cpp.

The constructor (WebSocketServer::WebSocketServer(...)) uses an initializer list to store the port, key, and cert paths. More importantly, it initializes the uWS::SSLApp app_ with an options struct
that includes the SSL file paths. After that, it just calls setup_routes() to configure all routes.

The setup_routes() method wires everything together:

  • It sets up a WebSocket handler for all paths using .ws<SocketData>("/*", {...}).
  • Each event (open, message, close) is handled by a lambda that captures this, allowing the callbacks to call member functions like this->on_open(ws).
  • It also defines a simple HTTP route /health to verify the server is up and running.

Finally, the run() method does two main things:

  1. It calls .listen(port_, ...) to bind to the port. The callback checks whether the bind succeeded (non-null socket) or failed (e.g. port in use).
  2. It chains .run(), which starts the server’s main event loop. This call blocks the main thread and keeps the server running until the process is terminated (for example, with Ctrl+C).

Step 3: The Entry Point

Update your src/main.cpp:

#include "include/WebSocketServer.hpp"
#include <iostream>

int main() {
    constexpr int PORT = 7000;

    try {
        WebSocketServer server(PORT, "../certs/key.pem", "../certs/cert.pem");
        server.run();

    } catch (const std::exception &e) {
        std::cerr << "Error: " << e.what() << "\n";
        return 1;
    }

    return 0;
}

Step 4: Build and Run

cmake -S thing if you haven’t yet ran it, otherwise you can just follow the cd build && make command.

# Build it
cmake -S . -B build
cd build && make

# Run it
./websocket_server

If you did everything correctly you should see something like:
You should see:

✓ WebSocket server listening on port 7000
✓ WSS endpoint: wss://localhost:7000
✓ Health check: https://localhost:7000/health

Holy shit. You just built a WebSocket server in C++.

This is just the surface of what you an do with µWebSockets.

Step 5: Testing Your WebSocket Server

Alright, we built it. Time to see if this beast actually talks.

Option 1: Using wscat

If you’ve got Node.js installed, you can use the wscat CLI to test your
server in seconds.

# Install wscat globally
npm install -g wscat

# Connect to your server (use --no-check to ignore self-signed certs)
wscat -c wss://localhost:7000 --no-check

If everything’s working, you should see something like this:

connected (press CTRL+C to quit)
< {"event":"connected","message":"Welcome!"}

Now type anything you want:

> hello there
< hello there

Boom. It echoes back exactly what you sent. That’s your WebSocket server

Image

Lessons Learned (aka: Things That Made Me Question My Life Choices)

So you’re load-testing your shiny new WebSocket server, and suddenly…
it dies at ~1015 connections. Every. Single. Time.

If this happens, relax. It’s not your code. It’s your OS.

By default, most systems cap the number of open file descriptors per process (ulimit -n) to around 1024. Each WebSocket connection eats one.
Do the math.

fix is simple:

# Temporary fix (resets on reboot)
ulimit -n 65535

Fun fact: Node.js’s ws implementation quietly handles this under the
hood, which is why you’ll see it scale past 5k connections without
breaking a sweat.
C++ makes you earn that performance the hard way.


Wrapping Up

If you made it this far, congratulations, you just built a fully encrypted,
high-performance WebSocket backend in pure C++.

“Why write a backend in C++?”

Because we can.
And now, so can you.