Someone built a production web backend in raw C++20. No frameworks. No libraries. Just sockets, HTTP parsing, and 472 lines of code. The binary is under 500KB. Memory usage sits below 10MB. Cold start is under 10 milliseconds. It runs on a $6 VPS and handles HTTPS traffic without breaking.
This shouldn't work. Modern backend development assumes you need frameworks, ORMs, middleware stacks, and dependency trees that pull in half of npm. This project proves you can ship a working backend with none of it. And the tradeoffs are more interesting than you'd expect.
What Raw C++20 Means in Practice
Raw means no framework. No Express, no Flask, no Rails. You open a socket yourself. You parse HTTP headers yourself. You handle keep-alive yourself. You implement routing as a switch statement. Every line of code is yours to debug and yours to understand.
The HTTP parser is 80 lines. It reads the request line, splits headers on colons, extracts the method and path, and builds a struct. No regex. No string allocations unless necessary. If the request is malformed, it returns 400. If the method isn't GET or POST, it returns 405. That's the entire parser.
Routing is a switch on the path string. Four endpoints: health check, user creation, user retrieval, login. Each case calls a handler function. Handlers return a response struct with status code, headers, and body. The main loop serialises it back to the socket. No middleware. No plugins. No abstractions.
The result: you can read the entire request-response cycle in one sitting. There's no magic. No framework doing things behind your back. If a bug appears, you know exactly where to look because you wrote every line that runs.
The Deployment Stack That Actually Works
The backend runs on a $6 DigitalOcean VPS. Ubuntu 24.04. The C++20 binary is compiled locally, uploaded via rsync, and managed by systemd. That's the whole deployment pipeline. No Docker. No Kubernetes. No CI/CD beyond a shell script that builds and pushes.
Nginx sits in front as a reverse proxy. It handles TLS termination with a Let's Encrypt certificate. Requests come in on port 443, Nginx decrypts them, forwards to localhost:8080 where the C++ backend listens, gets the response, encrypts it, and sends it back. The backend never touches TLS. It doesn't need to - Nginx is better at it anyway.
Systemd keeps the process alive. If it crashes, systemd restarts it. Logs go to journalctl. You can tail them with journalctl -u backend -f. No log aggregation service. No observability platform. Just systemd doing what systemd does.
This stack is boring. That's the point. Boring stacks don't break in weird ways. You ssh into the box, check the logs, restart the service if needed, and move on. No orchestration layers. No distributed tracing. No service mesh. Just a binary, a reverse proxy, and a process manager.
What You Lose and What You Gain
What you lose: Frameworks handle edge cases you didn't know existed. Raw C++ means you handle them yourself or ignore them. Content-Length mismatches, chunked encoding, multipart form data, URL encoding corner cases - every HTTP quirk is your problem now. If you don't test for it, it's a bug waiting to happen.
You also lose velocity on new features. Adding authentication required writing a JWT parser by hand. Adding JSON support meant integrating a tiny JSON library. Every feature that would be one line in Express is 50 lines in raw C++. That's fine for small projects. It's a nightmare for fast-moving teams.
What you gain: Performance. The cold start is 10 milliseconds because there's no framework initialisation. Memory is 10MB because there's no object pool, no middleware stack, no ORM holding connections. The binary is 500KB because there are no dependencies. This backend could run on a Raspberry Pi Zero and still respond in single-digit milliseconds.
You also gain understanding. Building this forces you to learn what HTTP actually is. Not the abstraction Express gives you, but the protocol itself. You learn how sockets work. How TLS termination works. How systemd manages processes. How Nginx buffers requests. These are foundational skills that transfer everywhere.
When This Approach Makes Sense
This is not a general-purpose backend strategy. It makes sense for small, performance-critical services that don't change often. An API gateway. A health check endpoint. A metrics collector. A webhook receiver. Anything where the requirements are stable and the performance matters.
It also makes sense for learning. If you've only ever used frameworks, building a backend from scratch teaches you what the frameworks are hiding. You'll appreciate Express more after writing your own HTTP parser. You'll understand why ORMs exist after manually escaping SQL strings for the fifth time.
But for most teams, most of the time, frameworks are the right choice. They handle edge cases. They provide security defaults. They let junior developers ship features without learning socket programming. The 500KB binary is impressive, but the 5-minute feature implementation in Django is often more valuable.
The Bigger Lesson
The lesson isn't "everyone should write backends in raw C++". It's "most backends don't need what they're using". A typical Node.js backend pulls in 500MB of dependencies to serve JSON over HTTP. This one does it in 500KB. The gap between those numbers is waste - unnecessary abstraction, unneeded features, complexity for complexity's sake.
You don't have to go full raw C++20 to benefit from this thinking. But you can ask: What could we remove? Which dependencies do we actually need? Could this service be smaller, faster, simpler? The answer is usually yes. And the team that figures out how to ship 10MB services instead of 500MB services has a real advantage - lower costs, faster deploys, fewer failure modes, and software they can actually understand.