Every six months or so, someone asks us why a serious operational
software firm is still building on PHP in 2024. The question comes
from an honest place. The tech press has been declaring PHP dead for a
decade. The hiring market for senior PHP engineers in India has
narrowed. The shiny defaults at every modern startup are Node, Go, or
Rust on the backend and React or Next on the frontend.
We do use Next.js on the frontend, including on this site. We do use
modern tooling in our build pipelines, our deployments, our
monitoring. We are not nostalgic about technology choices.
We still build the operational backends on FrankenPHP. Here is why.
The technology choice we are actually making
The framing of "PHP versus Node" or "PHP versus Go" misses what the
choice is actually about. When we choose FrankenPHP, we are not
choosing the language. We are choosing the operational characteristics
of the runtime: ubiquitous deployment surface, mature ecosystem for
the kinds of CRUD applications we build, low operational overhead,
straightforward concurrency model for the workload, and a maintenance
posture that does not break every six months because the framework
team decided to redesign their lifecycle hooks.
For the operational software we build, those characteristics matter
more than language preferences. Most of our applications are not
high-throughput real-time systems. They are CRUD applications with
some integration glue, some reporting, some background jobs, and a
clean web interface. PHP is exceptionally well-shaped for that
profile.
FrankenPHP specifically gives us the runtime characteristics we
actually want. It is a single-binary deployment with Caddy as the
embedded server. It handles HTTP/2 and HTTP/3 natively. It supports
worker mode for performance-sensitive applications. It runs PHP code
with modern performance characteristics that close most of the gap to
Node-equivalent benchmarks. And it does all of this without forcing
us to manage a separate web server, a separate process manager, and a
separate set of configuration files.
The maintenance argument that nobody makes loudly
The maintenance argument is the one we care about most, and the one
that does not get discussed enough.
A PHP application written carefully five years ago runs, with minimal
changes, today. The language has evolved. The frameworks have
evolved. But the upgrade path is gradual and the breaking changes
are limited. A Laravel 8 application can be brought to Laravel 11
across several deliberate weekends. The same is true of plain PHP
applications.
A Node application written carefully five years ago is significantly
harder to bring forward. The framework defaults have changed several
times. The bundler has changed several times. The package ecosystem
has churned through entire epochs of design. Most production Node
applications from 2019 require substantial rework to run on current
infrastructure with current security patches.
For the operational software we build, the applications need to run
for years. A client whose ERP we deployed in 2020 expects that
system to run in 2025 without a forced rebuild. PHP makes that
straightforward. Many alternatives do not.
The hiring argument we deliberately rejected
The counter-argument we hear most is about hiring. PHP developers are
harder to find than Node developers in the current Indian market.
The fashion is the other direction. New graduates want to work on
Node and React because that is what the visible startups use.
We have decided this is the wrong frame for us. We do not need to
hire dozens of PHP developers at scale. We have a small team of
senior engineers who care about the operational characteristics of
the systems they build. The hiring pool we need is small, deep, and
made up of people who specifically want to do the kind of work we do.
We have also found that strong engineers, regardless of language
background, can be productive in our stack within a few weeks. PHP
in 2024 is a reasonable language. The framework conventions are well
understood. The tooling is mature. Onboarding is not the bottleneck
people expect it to be.
What we use on top of FrankenPHP
For full transparency, the stack we ship most often is FrankenPHP for
the application runtime, PostgreSQL for the data layer (we left MySQL
behind for new builds in 2022), Mercure for the real-time updates
that some modules need, Caddy as the embedded edge proxy (it ships
with FrankenPHP), Redis for caching and queues, and Tailwind for the
frontend. Modern, opinionated, deployable as a small set of
containers on any KVM VPS. The
AK Suppliers & Distributors build
runs the full stack in production today.
For the parts of the system that need real-time client updates
beyond what Mercure handles well, we add a lightweight WebSocket
service. For PDF generation, we use Gotenberg. For search, Typesense.
For internal analytics, Metabase. Each of these is a focused tool
chosen for the role.
For the frontends of new applications, we are increasingly using
Next.js, including for this website. The combination of a Next
frontend with a FrankenPHP API backend gives us the best of both
worlds: a modern interactive interface with a maintenance-friendly
operational backend.
The point is the trade-off, not the language
This essay is not a defence of PHP for every use case. If we were
building a high-frequency trading system, we would not use it. If we
were building a real-time game backend, we would not use it. If we
were starting a Node-native team in 2024 with no constraints, we
might pick differently.
For the work we actually do, which is operational software for
founder-led businesses, on infrastructure they own, that has to run
reliably for years with low operational overhead and a clean upgrade
path, FrankenPHP is the right tool. The fashion is not the right
input to the decision. The trade-offs are.
If your tooling decisions are driven by fashion, you will rebuild
your stack every three years. If they are driven by trade-offs, your
clients will get systems that run for a decade.