I can't read any of the text on that page other than the headings. Is that what makes it modern? I assume the stack mentioned in the blog post is vibe-slop as well.
I wanted to try my hands at a monochromatic website and chose two shades that were far enough apart to pass a AAA contrast rating for accessibility. I'll try to improve it soon.
My eyes aren't particularly bad, not at the point where I need glasses to read books, program or anything, but I'm at 160-170% zoom before it's comfortable to read. HN is the same... so it might just be me.
From all the programs I use, the ones written in C are the most reliable (such as vim, bash, coreutils, terminals, X11¹, ... also larger software such as gimp, blender). Over decades I literally can't experience a single crash!
¹ The exception is the Nouveau driver, but without it X was also perfectly stable for me.
As someone who works on C compilers, I find the obession with C language insane.
For any greenfield project, pick up C++ in lieu of C if you want a C language.
There is no runtime penalty for using C++ if you stick to plain structs and don't use virtual functions, but you get better static type safety, namespaces, etc. even if you don't use the OO features.
Even for virtual functions, the overhead is minimal that it is just noise.
C compilers themselves are written in C++ for the last 15 years. Oh, the irony!
I generally like "C-style C++", but I find it difficult to draw the line between C and C++. It's too easy to keep reaching into C++ for "just one more thing."
So use the C standard library instead of the C++ one. You still get RAII / constructors/destructors and runtime polymorphism, along with stronger compile-time type safety.
I frequently do that myself on simple C++ programs just for compile time reasons alone, where I'll use stdio.h instead of std::iostream or std::format.
caveat: Amazon was just a simple online bookstore at the time. There was little relation to what amazon is now (no AWS for example) except it was already selling and shipping stuff and keeping an inventory like any other online store.
actually, in 1994/95, there was (almost) no inventory.
we invented a system we called "almost in time", the basic principle of which was "no we don't have that in stock but we can get it for you really fast". this is why we had to write our own system, because all the existing inventory mgmt systems (at least at that time) assumed that you would have at least some stuff in stock.
Because in practice it’s really hard to not succumb to C++ feature creep. You start with good intentions and limit yourself to namespaces and structs, but then the “this will benefit from a vector, or this from a map” appears, then you realize you’d like a templated use case, and a single raw pointer or resource handle from a call to something like fopen() suddenly is not releasing its resource on a linked list pop and now you’re writing move semantics on top of copy semantics because a destructor declaration silently enforces copies on a std::move without move semantics. You reach for a unique pointer and, and all of a sudden, you have most of modern C++ without a single virtual call.
So people obsess over C because you’re hard limited by the upper feature bound. And that’s not so bad because C gets feature updates too - microscopic but effective feature updates; see C29 for the poor man’s RAII: defer
and what it does take away?
most "better C" languages basically looks like C++ (except zig, maybe).
Nobody write something real in them, so nobody knows about their pitfalls.
n=1 but I've probably written (sic - a lot of AI) over a million lines of it this year. I like it precisely because it is nothing like C++, it's really just a better C, and the compiler / tooling is almost as good as Go.
For any greenfield project, pick up Rust instead of C++. Why? Ask any AI and it will pick Rust. Since AI is writing the code now and it likes Rust, because the pre-AI internet in its most trustworthy training dataset liked Rust. Therefore all code will eventually be Rust.
> For any greenfield project, pick up C++ in lieu of C if you want a C language.
Depends on the metric.
1. If the metric is "How many features can I have", then, sure, C++ wins - it has everything.
2. If the metric is "how few footguns are there", then C++ loses by a mile - there's too many ways that the different features from #1 above can interact with each other to make it viable to keep track of all the footguns when writing code.
You may not value having fewer footguns and/or lower cognitive burden, but many do.
The underlying libraries you use tend to be in C anyway, so it simplifies things to just use them with their native interface, and not having to add another language runtime and its idiosyncrasies.
A shame to hand code a project in C and then use AI to write a blog post.
It’s a cool project to test yourself but most of the benefits outlined would also apply to Go, Rust, etc, and then you also get memory safety thrown into the mix as well.
"Every request spawns a fresh process. Deploying a new version is replacing one file. That means no server restart and no downtime. New requests simply pick up the new binary."
I think that's why FastCGI exists. It is the equivalent of PHP's "Swoole" or "FrankenPHP" or "amphp" or Laravel's "octane", etc. Each process is long-lived and tries to clean up its state as much as possible between requests. Forking a new process can take around 8ms (https://news.ycombinator.com/item?id=19005586)
When I was building https://github.com/Qbix/webserver in PHP, I was faced with forking a new process per request. I implemented fork-per-request at first, similar to PHP-FPM and then implemented the "FastCGI" approach of long-lived processes that clean up after each request. (Modern PHP has introspection so the engine can know what to clean up, before calling the php files to handle things.) However, some things are not really possible to completely reset in PHP, and you have to be careful with database connection pools, etc. Typically those are left open even by preforking processes like php-fpm.
Anyway, I ended up flipping it: use the "FastCGI" approach as a default, and if the original PHP code doesn't work (rare), then for that app, you can choose the regular CGI approach of forking a process per request. That way you get maximum speed by default, and manually opt into safety (this is an exception to my usual priorities).
PHP is actually quite fast. But I was also interested in transpiling it to C. So I built the transpiler at: https://ulanguage.org/playground ... used Claude to transform about 1000 PHP functions to their native equivalents, but if you use any of the others, it would hook into the Zend runtime and link with it. (There are some nuances with zvals and objects, and resources in previous PHP versions, but I managed to overcome them.) By the way, it also works with Node and Python FFI the same way.
You can basically now take PHP, Python or Javascript/Typescript code, and speed up the CPU-bound parts about 10-50x. A lot of that is because of string and JSON handling. Many web apps use strings and JSON a lot. So underneath, I had the compiler use "ropes" for the strings, and avoid concatenation. It also parallelizes the JSON encode and decode into code that can run on multiple cores at once, i.e. large JSON payloads are encoded and decoded in parallel. Those are just huge speedups.
Anyway... all of this is mostly focused on speed optimization, so you can solve the literally C100K problem (forget C10K) today, on a regular box. If you don't need speed or FastCGI, your approach is fast enough for the vast majority of applications.
FastCGI sounds like a perfect fit for Tiger Style and Zig. You can avoid allocations completely if you set up a fixed size arena and reset it after each request.
There's also methods of starting Rack applications directly form nginx via passenger[1]. Rack is the CGI like interface for ruby webapps: https://github.com/rack/rack
It easily allows for writing applications and middleware with a few lines of ruby.
You can write a ngninx module.
That would be the actual 2026 way to write C web apps.
Used to be a huge pain, but now AI can just read the nginx source and do it correctly.
SQLite can handle multiple concurrent requests if it's set to WAL mode. Reading is all non-blocking/concurrent but writing is still synchronous. You should also set the "busy timeout" or else your blocked writes will timeout immediately.
SQLite does allow write access from mulitple processes - file locks (fcntl) are used to ensure at any given moment only one of processes modifies the database (or WAL if WAL is enabled).
In Wal mode you can have multiple readers. Writers block, but because there is no network traffic, the writers finish very quickly, so in practice you don't usually wait that long.
tell me where working with strings is not miserable?
Practically, modern languages with RAII make it easy at the cost of perfomance (doing a lot of allocations).
So, if you doing lot of string handling, you still need to think how to do it in performant way. If doing it a bit - why care about miserable.
You can "have your cake and eat it too" in Rust, C#, and I suppose C++ if you're a masochist.
These modern languages have both easy-to-use "owning" string types and a "string slice" type that can be passed around efficiently. Unlike C, in these languages a string and a slice are largely interchangeable, which makes them easier to use throughout not just your own codebase, but third-party libraries too.
I still like writing web applications in PHP for many of the same reasons listed here - modern PHP anyways.
I shy away from frameworks unless I am building something that I expect a large team to work on, or I specifically need multiple complex features that don't make sense to maintain.
I've written a few applications for my own personal use or targeted at friends/family recently that have no third party dependencies other than Bootstrap, which I still like more than tailwind for anything that doesn't need to look like a marketing page.
For people who find interest in weird web applications, I have been working on a game project, in C++, where the client is compiled to WebAssembly with Emscripten, and the application server is also implemented in C++ (behind nginx).
This is from pre-AI era also. The game is Civ-like. Custom engine from scratch (including both the 3D stuff, and all the web handling also).
I'm doing something similar but with a Red Alert++ type of game with C and WebAssembly and Emscripten.
It's something I've been picking at in my free time but these posts from the last week regarding game decompiling old games and rebuilding the as web apps have inspired me to pick it up again. It's interesting to me how many people were critical of the performance of these games and that recent Sim City clone definitely had my laptop fans whirring but I find that my game doesn't have any of these performance issues. Performance-wise WebAssembly and Emscripten seem remarkable, and I haven't even added SIMD or multicore functionality yet.
One of the nice features about building it this way is that the desktop version can easily interface with 3d model / image generators which I intend to in the desktop unit-editor to generate assets for the game.
The pipeline that I'm envisioning is a 3d model generator that pushes the models into Blender to generate different perspective stills of them and then feed that to a stable diffusion image generator that paints them and a unit icons and such, and a voice model that generates their "ready to serve" peon type sounds when you click on them. I'm picturing a system that I can leave over night unattended to generate a bunch of units that I can look at in the morning to add to the game if I like them.
Everything the post mentions is 100% true and well-reasoned, especially with the inclusion of pledge/chroot/etc. for dropping privileges.
There's two diametrically opposed deployment mechanisms:
1) stack towers of glop on top of each other until you're running a simple `echo "hello world"` on top of a docker container inside a vm in a proxmox server as part of a k8s cluster under a hypervisor running on remote compute...
2) just run `bash -c 'echo "hello world"'` and front it with CGI (stateless, single binary, yada yada yada...)
Take a peek at https://github.com/Domain/bash.cgi ... not that i'm suggesting that 'bash over busybox using cgi-bin' is the "proper architecture", it's more trying to illustrate that the amount of code that holds you back from being nearly truly "frameworkless" is tiny and easy to reason about.
For some of the internal tooling I'm doing for work, I've been tending towards:
some-cmd [--json|--markdown] ...yet-more-args...
When you use the process and shell environment itself as a repl, you start trending towards `[--cgi-bin]` would "just" be another output type, and the ability (nay: foresight!) to ensure that your commands are independent, reproducible, and only depend on their input arguments leads you directly towards being runnable in both "hostile" (ie: firecracker vm / lambda-style) or very "comfortable" environments (ie: shell, debuggers, unit/integration tests).
Mirrors my experience, and it's funny you mention busybox: I just discovered busybox's ability to serve via `busybox httpd -p 9000 -h . -f -v` with a `cgi-bin` folder dropped in the current directory.
I already write Python services using Bottle that function both as a standalone server and as a CGI script, and I've been updating them to work with busybox's more constrained CGI environment (basically being able to handle the lack of .htaccess rewrite rules).
I definitely see a chance for simple deployment tools to make a resurgence as we create more one-off/disposable/personal software, and the low system requirements for something like busybox + lua stack are extremely minimal, which fits well with the current DDR4-is-good-enough zeitgeist. I'm running them on an old Raspberry Pi 2B and it's plenty performant for the ~10 people that access it regularly.
Event driven, fast, all the networking basics in place including http, mustache templates and encryption, light and small executable, very nice for embedded applications, signals and fd handling built-in, very easy to use…
Have used it very successfully for million-dollar projects, including some scientific applications running on limited-resource systems (512 megs of RAM) ..
Was very, very easy to add C functions to interface with the web front-end, libffi is great for this purpose, but its also very easy to extend as a standard, well-written Lua/C application ..
This is exactly the sort of thing I'm really interested in. I'm particularly excited about various takes on Lua+batteries, have looked into designing my own (C wrapper with bindings to networking, JSON, templating, and DB libraries), but found Redbean to be almost ideal so never got past overall design to dig into code.
Turbo looks fantastic, though! It's precisely the niche I was looking to have filled, so I'll build a prototype or two in it to see what I think. Very much appreciate the pointer!
PS I do seem to remember it was either disavowed or inexact. Notwithstanding, it is a bit like Twain or Einstein, they get stuck with things that they could reasonably indeed have said.-
Fantastic, thanks. I love Lua and especially the mindset that goes along with it. Will definitely check this out. Combining it with Datastar could be brilliant - I wonder if TurboLua supports SSE streaming.
This is the first I am hearing about turbo lua, very cool! I was aware of openresty but I still resorted to flask for quick endpoints or concepts. This changes things for me.
I'm still in the middle of reading but I wanted to comment first that I had a pleasantly surprising experience a few years ago with CGI. I hadn't thought about CGI since I was a kid and I was tasked with adding a simple dynamic page to a server that had nothing running yet except apache. For whatever reason I though to try out CGI because of how simple the page was going to be, and yeah. Would recommend
I just got C FFI working in my LLVM based language last night so I'll need to take a look at playing around with BCHS :)
I learnt Perl because I had to make a page with a form that would then be turned into Postscript and printed. My only options were C, which I knew well, and Perl, which I had never seen before. So I got to learning Perl. Doing it in C just seemed like too much work for the task.
I dropped Pascal programs in there, because it was the language I knew best, good times! It was really slow, too, because the bundled runtime had a massive startup time.
A Go binary with CGO=0 and all templates/documents embedded gives you a better overall tooling and deployment experience. Put it in a scratch container for complete isolation.
Early PHP (3 and 4) were the thin layer over the C libraries and were pretty fast and easy. it came about right when CGI was deprecated and it solved CGI's problems
I was not thinking about vanilla C and CGI but I wrote my own modern web api server app in C++ (std+boost only) and I found it extremely pleasant experience. very reliable and little supply chain/dependency hell problematic too.
It’s for intranet business app so although it’s not yet production ready yet, definitively winning against many other modern approaches in web development in my consideration, and quite refreshing POC project.
Yeah, shuddering at trying to manage memory vs static strings (with OOB risks) in plain C, modern C++ does give so much.
I'm actually also rewriting a header-only server (std c++ and sockets) I wrote a few years back to take advantage of C++26 reflections and similar to give it something similar in capability to AspNetCore+EFCore.
318 comments
[ 3.4 ms ] story [ 86.1 ms ] threadI can't read any of the text on that page other than the headings. Is that what makes it modern? I assume the stack mentioned in the blog post is vibe-slop as well.
It was 100% human slopped :)
[0] - the other one: https://news.ycombinator.com/item?id=50051573
¹ The exception is the Nouveau driver, but without it X was also perfectly stable for me.
For any greenfield project, pick up C++ in lieu of C if you want a C language.
There is no runtime penalty for using C++ if you stick to plain structs and don't use virtual functions, but you get better static type safety, namespaces, etc. even if you don't use the OO features.
Even for virtual functions, the overhead is minimal that it is just noise.
C compilers themselves are written in C++ for the last 15 years. Oh, the irony!
That may be a me problem.
Large portions of the standard lib depend on heap allocations. C is a much safer target for environments with limited resources.
I frequently do that myself on simple C++ programs just for compile time reasons alone, where I'll use stdio.h instead of std::iostream or std::format.
we invented a system we called "almost in time", the basic principle of which was "no we don't have that in stock but we can get it for you really fast". this is why we had to write our own system, because all the existing inventory mgmt systems (at least at that time) assumed that you would have at least some stuff in stock.
I've had friends finding photos of their own bicycle locked in the street advertized in second hand platforms.
So people obsess over C because you’re hard limited by the upper feature bound. And that’s not so bad because C gets feature updates too - microscopic but effective feature updates; see C29 for the poor man’s RAII: defer
n=1 but I've probably written (sic - a lot of AI) over a million lines of it this year. I like it precisely because it is nothing like C++, it's really just a better C, and the compiler / tooling is almost as good as Go.
Eh? What's the point of a `fn` keyword if I can't `grep -r "fn foo"`?
Depends on the metric.
1. If the metric is "How many features can I have", then, sure, C++ wins - it has everything.
2. If the metric is "how few footguns are there", then C++ loses by a mile - there's too many ways that the different features from #1 above can interact with each other to make it viable to keep track of all the footguns when writing code.
You may not value having fewer footguns and/or lower cognitive burden, but many do.
You can do that in other languages but you need to use aditionnal dependencies (modules or bindings).
It’s a cool project to test yourself but most of the benefits outlined would also apply to Go, Rust, etc, and then you also get memory safety thrown into the mix as well.
I think that's why FastCGI exists. It is the equivalent of PHP's "Swoole" or "FrankenPHP" or "amphp" or Laravel's "octane", etc. Each process is long-lived and tries to clean up its state as much as possible between requests. Forking a new process can take around 8ms (https://news.ycombinator.com/item?id=19005586)
When I was building https://github.com/Qbix/webserver in PHP, I was faced with forking a new process per request. I implemented fork-per-request at first, similar to PHP-FPM and then implemented the "FastCGI" approach of long-lived processes that clean up after each request. (Modern PHP has introspection so the engine can know what to clean up, before calling the php files to handle things.) However, some things are not really possible to completely reset in PHP, and you have to be careful with database connection pools, etc. Typically those are left open even by preforking processes like php-fpm.
Anyway, I ended up flipping it: use the "FastCGI" approach as a default, and if the original PHP code doesn't work (rare), then for that app, you can choose the regular CGI approach of forking a process per request. That way you get maximum speed by default, and manually opt into safety (this is an exception to my usual priorities).
PHP is actually quite fast. But I was also interested in transpiling it to C. So I built the transpiler at: https://ulanguage.org/playground ... used Claude to transform about 1000 PHP functions to their native equivalents, but if you use any of the others, it would hook into the Zend runtime and link with it. (There are some nuances with zvals and objects, and resources in previous PHP versions, but I managed to overcome them.) By the way, it also works with Node and Python FFI the same way.
You can basically now take PHP, Python or Javascript/Typescript code, and speed up the CPU-bound parts about 10-50x. A lot of that is because of string and JSON handling. Many web apps use strings and JSON a lot. So underneath, I had the compiler use "ropes" for the strings, and avoid concatenation. It also parallelizes the JSON encode and decode into code that can run on multiple cores at once, i.e. large JSON payloads are encoded and decoded in parallel. Those are just huge speedups.
Anyway... all of this is mostly focused on speed optimization, so you can solve the literally C100K problem (forget C10K) today, on a regular box. If you don't need speed or FastCGI, your approach is fast enough for the vast majority of applications.
if not, you could have sent patches and have the best of both worlds
My goal for speed was to run all existing unmodified PHP code out there, but handle 10x as many requests and throughput
Well, and also a ton of other features too
I like this project for adding CGI to nginx: https://github.com/pjincz/nginx-cgi
There's also methods of starting Rack applications directly form nginx via passenger[1]. Rack is the CGI like interface for ruby webapps: https://github.com/rack/rack It easily allows for writing applications and middleware with a few lines of ruby.
[1]: https://www.phusionpassenger.com/documentation/Users%20guide...
I have my own configuration template for nginx: https://git.levitati.ng/Levitating/nginx.conf
The answer is: it does but via community plugins.
> Every request spawns a fresh process.
How does it work when multiple concurrent requests are possible? Does SQLite allow simultaneous write access from multiple processes?
Um, any and all string handling in C is pretty miserable.
These modern languages have both easy-to-use "owning" string types and a "string slice" type that can be passed around efficiently. Unlike C, in these languages a string and a slice are largely interchangeable, which makes them easier to use throughout not just your own codebase, but third-party libraries too.
For example, the Rust `httparse` crate `Request` type returns parsed fields as string slices, not strings: https://docs.rs/httparse/latest/httparse/struct.Request.html
I shy away from frameworks unless I am building something that I expect a large team to work on, or I specifically need multiple complex features that don't make sense to maintain.
I've written a few applications for my own personal use or targeted at friends/family recently that have no third party dependencies other than Bootstrap, which I still like more than tailwind for anything that doesn't need to look like a marketing page.
This is from pre-AI era also. The game is Civ-like. Custom engine from scratch (including both the 3D stuff, and all the web handling also).
Take a look: https://statebuilder.com/
It's something I've been picking at in my free time but these posts from the last week regarding game decompiling old games and rebuilding the as web apps have inspired me to pick it up again. It's interesting to me how many people were critical of the performance of these games and that recent Sim City clone definitely had my laptop fans whirring but I find that my game doesn't have any of these performance issues. Performance-wise WebAssembly and Emscripten seem remarkable, and I haven't even added SIMD or multicore functionality yet.
One of the nice features about building it this way is that the desktop version can easily interface with 3d model / image generators which I intend to in the desktop unit-editor to generate assets for the game.
The pipeline that I'm envisioning is a 3d model generator that pushes the models into Blender to generate different perspective stills of them and then feed that to a stable diffusion image generator that paints them and a unit icons and such, and a voice model that generates their "ready to serve" peon type sounds when you click on them. I'm picturing a system that I can leave over night unattended to generate a bunch of units that I can look at in the morning to add to the game if I like them.
https://imgur.com/a/d65UBNu
https://github.com/codazoda/cgi-lib
Everything the post mentions is 100% true and well-reasoned, especially with the inclusion of pledge/chroot/etc. for dropping privileges.
There's two diametrically opposed deployment mechanisms:
1) stack towers of glop on top of each other until you're running a simple `echo "hello world"` on top of a docker container inside a vm in a proxmox server as part of a k8s cluster under a hypervisor running on remote compute...
2) just run `bash -c 'echo "hello world"'` and front it with CGI (stateless, single binary, yada yada yada...)
Take a peek at https://github.com/Domain/bash.cgi ... not that i'm suggesting that 'bash over busybox using cgi-bin' is the "proper architecture", it's more trying to illustrate that the amount of code that holds you back from being nearly truly "frameworkless" is tiny and easy to reason about.
For some of the internal tooling I'm doing for work, I've been tending towards:
When you use the process and shell environment itself as a repl, you start trending towards `[--cgi-bin]` would "just" be another output type, and the ability (nay: foresight!) to ensure that your commands are independent, reproducible, and only depend on their input arguments leads you directly towards being runnable in both "hostile" (ie: firecracker vm / lambda-style) or very "comfortable" environments (ie: shell, debuggers, unit/integration tests).I already write Python services using Bottle that function both as a standalone server and as a CGI script, and I've been updating them to work with busybox's more constrained CGI environment (basically being able to handle the lack of .htaccess rewrite rules).
I definitely see a chance for simple deployment tools to make a resurgence as we create more one-off/disposable/personal software, and the low system requirements for something like busybox + lua stack are extremely minimal, which fits well with the current DDR4-is-good-enough zeitgeist. I'm running them on an old Raspberry Pi 2B and it's plenty performant for the ~10 people that access it regularly.
https://turbo.readthedocs.io/en/latest/
Event driven, fast, all the networking basics in place including http, mustache templates and encryption, light and small executable, very nice for embedded applications, signals and fd handling built-in, very easy to use…
Have used it very successfully for million-dollar projects, including some scientific applications running on limited-resource systems (512 megs of RAM) ..
Was very, very easy to add C functions to interface with the web front-end, libffi is great for this purpose, but its also very easy to extend as a standard, well-written Lua/C application ..
Turbo looks fantastic, though! It's precisely the niche I was looking to have filled, so I'll build a prototype or two in it to see what I think. Very much appreciate the pointer!
I am offended on behalf of all for whom that is more RAM than their first computer had :)
Seemed like a lot when I only had 2MB…
PS I do seem to remember it was either disavowed or inexact. Notwithstanding, it is a bit like Twain or Einstein, they get stuck with things that they could reasonably indeed have said.-
512 megs is not a lot for an embedded Linux project these days, but it can do a lot. Especially if you use Lua. ;)
421.4K static-pie linked executable on amd64
I just got C FFI working in my LLVM based language last night so I'll need to take a look at playing around with BCHS :)
Give it a shot; I bet you'd like it a lot.
But the very first CGI scripts I wrote were in C, late 95 or early 96. Apache on Slackware Linux.
https://jimlawless.net/articles/ddj95/index.html
For example, it wasn't uncommon to have C extensions for Perl scripts dropped into CGI directory.
Early PHP (3 and 4) were the thin layer over the C libraries and were pretty fast and easy. it came about right when CGI was deprecated and it solved CGI's problems
It’s for intranet business app so although it’s not yet production ready yet, definitively winning against many other modern approaches in web development in my consideration, and quite refreshing POC project.
I'm actually also rewriting a header-only server (std c++ and sockets) I wrote a few years back to take advantage of C++26 reflections and similar to give it something similar in capability to AspNetCore+EFCore.