The first HTTP server I wrote wasn't good... Hell, it wasn't fast. wasn't secure, and it WASN'T production-ready.
To be honest, it barely deserved the title "web server". It accepted a TCP connection, read a request, and sent back a small HTML page.
Yet in those few hundred lines of code, I learned a lot more about how the web actually works than I had after years of using web frameworks (NodeJS / GoLang).
Most developers experience HTTP through libraries.
app.get("/", (req, res) => {
res.send("Hello, World!");
});
That's perfectly fine, but behind those three lines is an enormous amount of machinery that most people don't see, and a lot of more people don't get to appreciate.
A socket has to be created. The operating system has to accept a connection. Bytes arrive over TCP. Bytes get parsed, and validated. Headers interpreted. The request line has to be split into its method, path, and protocol version.
A response has to be constructed according to the HTTP specification. The connection needs to be closed—or kept alive. None of this is (really) that complicated.
But it is hidden.
Once you've written even a tiny server yourself, network debugging becomes way easier.
A 400 Bad Request is no longer just an error message.
You understand that your parser rejected malformed input.
A 404 isn't something your framework invented.
It's simply a status code you decided to return.
Headers are not mysterious anymore, you know they're just text separated by carriage returns, finally the protocols becomes understandable because you've implemented part of it yourself.
One of the first surprises is how messy and unorganized real requests actually are. Browsers don't send identical headers, clients omit things, some send unexpected whitespace, or disconnect halfway through a request, or even intentionally send malformed data.
Writing a parser forces you to assume nothing, every byte has to be treated as untrusted. That's a lesson that extends far beyond networking, and teaches you about security practices.
Ironically, writing an HTTP server often makes you appreciate frameworks even more, because you understand exactly what they're saving you from.
The difference is that now you're making an informed trade-off, you know what you're saving, you're using a framework because you understand the complexity.
Keep it small, listen on a port and accept a connection.
Parse the request line and return a static HTML page.
That is enough.
No routing, templates, middleware, and especially not thousands of dependencies.
Just a socket and a curiosity about what happens underneath.
You will NEVER look at app.get("/") the same way again.