Crafd runs on PHP, MySQL, Redis, and Nginx.
That’s the whole stack.
No React. No Tailwind. No npm. No Composer. No build step.
No classes, either. Just functions.
To any modern dev, I know how that probably sounds.
So let me make my case.
Somewhere along the way, we decided a web page needed a toolchain.
You want a button on a page. First, install a few hundred packages you’ve never heard of, written by people you’ve never met, so a tool you didn’t ask for can turn your JavaScript into slightly different JavaScript.
Then add a framework to render the HTML the browser was already going to render.
Then a second framework to make the first one fast again.
Then a virtual DOM to protect you from the real one, which was fine.
Then a CSS framework so you never have to write CSS (which you do by writing 95% CSS).
Then classes. Then abstract classes. Then a factory for the classes and a provider for the factory. You wanted to save a row to a database. You now have a Repository.
Then a config file for each of those, and a file to configure the config files.
And every couple of years, all of it gets deprecated and you get to start all over again.
None of that was ever necessary.
The browser already knows how to render HTML. PHP already knows how to produce it. MySQL already knows how to store things. These were solved problems before most of the frameworks replacing them existed.
Here’s what Crafd looks like instead.
Every request comes in through index.php. It’s ten lines long. (I see you, Pieter Levels.)
It loads a config, then a bootstrap. The bootstrap looks at the URL and runs a controller. The controller loads a model if it needs one, then a view with the same name. Controllers, models, views. An idea from the seventies.
The router is 123 lines. The whole framework, if you want to call it that, is nineteen files and about 3,200 lines.
There are no classes. Not because classes are evil. Because a class always wants a parent, and the parent wants an interface, and three weeks later you’re maintaining a type system instead of a web application.
Every function is named after the file it lives in. models_drops_close. core_libraries_router_view. If you can read the name, you know where it is.
Every PHP file opens with the same line, which says: if you didn’t come through the front door, exit.
The CSS is one rule per line, properties in alphabetical order and loads inline within the page.
The JavaScript (all vanilla) talks to the server with fetch and changes the page with the DOM (the real one).
The only script we load from anywhere else is Stripe’s. I am not silly enough to hold card numbers.
There is no build step, so the code in the editor is the code in the browser. Save, refresh. Deploying is a push.
The whole app is a little over thirty thousand lines, tests and help docs included.
Do I sometimes write a function a framework would have written for me? Sure. It takes four lines and I can read all four.
Why does any of this matter?
Three reasons.
First, with my old-school MVC setup, the application still feels zippy like a modern day application.
Second, I believe in the open web. With Crafd, anyone can view source on any page in the browser and read it top to bottom. The comments are sentences. Nothing is generated or compiled. Nothing is hiding behind a bundler. View source works the same way it used to 15 years ago.
And the third reason is agents.
My unofficial “co-founders” for Crafd (Claude and Codex) never have to run npm install. They never have to wait on a build. They never read a stack trace through six layers of somebody else’s abstraction. They open a controller, read it like a short story, change it, and refresh the browser immediately.
Will I regret some of these choices? Maybe. If so, I’ll change them.
I’d rather add complexity when it’s needed than start with it.
I’m gambling on simple. I suspect more developers will make the same bet with time.
Then again, I was never technically a developer, so I don’t have much clout to lose.
Leave a Reply