Profile
Back to NewsBack
Hacker News 4 min
Reader Mode
A Modern Web App in C

A Modern Web App in C

6 hours ago

## WHAT I LIKED

Fundamentals over frameworks.

CGI isn't a framework to learn. The 1.1 specification was published in 2004 and hasn't changed since.

There's no API to internalize. The entire interface is a handful of fundamental HTTP concepts:

  • Request headers.
  • Request methods.
  • Query strings.
  • Body data.

If you understand HTTP, you already understand CGI.

It's language and platform agnostic. You learn it once and the knowledge is good forever.

Clear separation of concerns.

A modern web application typically puts a web server like nginx in front and reverse-proxies to an application server written in another language like Ruby, Python, Go, or Rust. That always felt a bit strange to me: you essentially have two web servers, and one of them is also responsible for your business logic.

CGI gives you a single web server that calls your business logic directly. It's clear who does what.

It makes debugging simpler, too. Your application is an ordinary program you can run from a shell: pipe it a request, watch stderr, step through it with gdb.

In a sense, CGI resembles serverless architecture: a supervisor invokes your code per request, hands it a bag of environment variables, and collects the result. CGI is serverless, minus the vendor (and surprise bills).

Minimal supply chain risk.

With the rise of autonomous agents and weekly attacks on package repositories, it feels reassuring to have very few dependencies.

Most modern tech stacks come with a package manager and an ecosystem to go along with it. It's convenient, but the dependency graph balloons. One or two direct dependencies can pull in hundreds of indirect ones, each maintained by somebody you've never heard of, with publish credentials protected by a password reused since 2015.

My supply chain is a C compiler and three libraries. Compromising it would require compromising libcurl... a project the entire internet watches, not a package with eleven weekly downloads and an owner who left in 2019.

Simple tooling.

I'm a minimalist at heart. I use classic Vim with zero plugins. That means no LSP, no autocomplete, no code formatters, and no AI.

Complex tech stacks demand complex tooling: they demand a language server to wade through the layers of abstraction. But this stack is simple enough a plain editor is all you need. The entire application is a directory of C files and a Makefile.

Open a file, read it, and you know what it does.

Great tooling is within reach.

When you do want tooling, it's not far. Sanitizers, valgrind, gdb, ctags, cscope... these are mature (well, geriatric) tools that have been refined for decades and they're a package install away on any system that can compile C.

The maturity goes further than the tools themselves: integration is built into many editors. Vim ships with native support for ctags, cscope and has a graphical gdb frontend (see :h Termdebug).

Writing HTML and CSS in C isn't too bad.

I was hesitant to write the entire frontend in C, but it wasn't nearly as painful as I imagined.

All too many times have I:

  • Fought vim to highlight Go's html/template syntax.
  • Fought helix to detect Go templates without breaking html detection.
  • Fought rustrover to recognize jinja templates for a project using askama.

Using C means there's no second language for your editor to half-detect, no mixed-syntax where highlighting falls apart, no plugin dance to get the two languages to cooperate.

It was refreshing to just write C. It isn't pretty, but neither is Tailwind.

SQLite is a breath of fresh air.

I've always defaulted to Postgres for web applications. It's a great database, but it comes with some operational burden. There's a server process to babysit and frequent releases to upgrade to.

SQLite has none of that. There's no process, no configuration, no dependencies. Your application opens the database file directly. The operational burden is minimal.

Chat with me