Skip to content

Server-Side Development: The Pendulum, Not the Ladder

Backend history is told as steady progress from CGI to serverless. What actually happened is an oscillation, and knowing which way it's currently swinging is more useful than the timeline.

Share
Geometric blocks along a glowing track, alternating between tight clusters and scattered pieces

The usual telling goes: CGI, then scripting languages, then frameworks, then Node, then microservices, then serverless. Each step is better than the last, arriving neatly at the present.

It's a tidy story, and it's wrong in an interesting way. Backend architecture doesn't ratchet forward. It oscillates, and the main oscillation is between putting everything in one deployable unit and splitting it into many. We've been around that loop more than once, and the current swing is heading back toward consolidation.

Knowing that is worth more than the timeline, because it tells you how to read whatever gets recommended to you next.

CGI, and the constraint that shaped everything

The Common Gateway Interface let a web server run a program and return its output. Mostly Perl and C, and it made dynamic pages possible at all.

The problem was the execution model: every request spawned a fresh operating system process. Process creation is expensive, so a site with real traffic spent most of its resources starting and stopping programs rather than serving anyone. That single constraint drove the next fifteen years of backend design.

Worth noticing that CGI's isolation was also a genuine virtue. No shared state between requests, so no memory leaks accumulating and no request contaminating another. Those properties got rediscovered later, which is a recurring theme here.

The fix was persistence, not the language

The standard story says PHP, ASP, and JSP improved on CGI because you could embed code in HTML. That's the visible part and not the important one.

The important part was that these ran inside a persistent process. mod_php embedded the interpreter in the web server. FastCGI kept worker processes alive between requests. Java application servers held a thread pool. The interpreter stopped being started fresh for every visitor, and throughput improved by orders of magnitude.

So the leap wasn't syntax. It was amortising startup cost, which is why the same era produced database connection pooling: the same idea applied one layer down.

Frameworks, and the productivity era

By the mid-2000s, the constraint had moved from machine time to developer time. Applications were more complex, teams were larger, and everyone was rewriting routing, templating, database access, and session handling from scratch.

Rails answered it with convention over configuration: agree on where things live and stop configuring. Django, Laravel, and Spring landed in the same territory. What they really provided was a set of shared defaults, so a new developer could open an unfamiliar codebase and find their way.

This era produced the classic monolith, and it's worth being clear that a monolith is a deployment decision, not a design failure. One deployable unit containing everything. Simple to run, reason about, and deploy.

Node and the concurrency shift

Node's contribution is often described as "JavaScript on the server," which undersells it. The significant part was the concurrency model: a single-threaded event loop instead of a thread or process per request.

For workloads that spend their time waiting on databases and APIs, which is most web work, that was dramatically more efficient. It also introduced a failure mode, since any CPU-heavy operation blocks everything. I compare that with the other models in the piece on server-side languages.

One correction worth making, because the original version of this story gets it backwards: jQuery, AngularJS, and React didn't challenge server-side scripting languages. They're browser libraries, and they solved a different problem. Node challenged the server-side languages, and it did it on concurrency, not on being JavaScript.

The swing outward: microservices

Then the pendulum went the other way, and the reasons were mostly organisational.

A large monolith with two hundred engineers committing to it becomes a coordination problem. Everyone waits for the same deploy, one bad change blocks everybody, and the codebase resists being reasoned about. Microservices addressed that by giving teams independent deployment: your service, your release schedule, your database.

That's a real benefit, and it's worth naming precisely, because it's an organisational benefit rather than a technical one. Netflix and Amazon adopted microservices to let hundreds of teams ship without coordinating, which is a problem most companies don't have.

The costs arrived on schedule. A function call became a network call, which can fail, time out, or arrive twice. Transactions spanning services stopped being transactions. Debugging required distributed tracing because no single log told the story. And the operational burden multiplied, since forty services need forty deployment pipelines, forty sets of alerts, and forty on-call rotations.

The worst outcome was the distributed monolith: services split apart but still so coupled that they had to be deployed together. All the network cost, none of the independence.

The swing back: modular monoliths

Which is where the pendulum is now, and the reversal is well documented.

Amazon's own Prime Video team published a case study on moving a serverless microservices pipeline back to a monolith and cutting costs substantially, which was notable mostly for who was saying it. Plenty of companies have followed the same path more quietly, and a modest amount of cloud repatriation has followed.

The current consensus, roughly: start with a monolith, keep it modular internally with clear boundaries between domains, and extract a service only when you have a specific reason. Genuinely different scaling profile, a team that needs independent deployment, or an isolation requirement.

The important word is "start." Microservices are an answer to organisational scale, and adopting them before you have the organisation means paying every cost for none of the benefit.

Containers, which actually changed things

Underneath both swings, the thing that genuinely accumulated.

Docker made "it works on my machine" mostly go away by packaging the application with its dependencies. Kubernetes made running many of those packages a solved-ish problem. Together they made microservices operationally feasible, and they made monoliths easier to deploy too.

This is worth separating from the architectural pendulum, because containers didn't swing back. They're one of the few genuine ratchets in this story: deployment packaging improved and stayed improved.

Serverless, and where it settled

Serverless promised no infrastructure to manage, per-request billing, and automatic scaling. Some of that held up.

Where it works well: irregular or spiky traffic, event-driven processing, glue between services, and anything where idle capacity would otherwise be wasted. Scale-to-zero is genuinely valuable for workloads that are quiet most of the time.

Where it stalled: cold starts hurt latency-sensitive paths, execution limits rule out long-running work, per-request pricing becomes expensive at sustained high volume, local development is awkward, and vendor coupling is real. "No infrastructure management" also overstated it, since you're managing a different kind of infrastructure with a smaller vocabulary.

So serverless found a place rather than becoming the destination. It's one deployment option among several, and the ascending-arrow version of this history that ends at serverless as the final form was never right.

The pattern underneath

Read the whole sequence, and the driver isn't technology. It's the size of the deployable unit, and it moves with team size and operational cost.

CGI was maximally granular, one process per request, driven by simplicity. Application servers consolidated, driven by performance. Monoliths consolidated further, driven by developer productivity. Microservices dispersed, driven by organisational scale. Serverless dispersed further, driven by billing. Modular monoliths are consolidating again, driven by operational cost.

Every swing solved a real problem and created the conditions for the next one. That's the same shape the client-side story has, which I set out in the piece on what the predictions got wrong, and it's why the rendering question moved the same way, from server to client and back again.

The useful consequence: when something is presented to you as the way things are done now, ask which problem it solves and whether you have that problem. Microservices solve coordination overhead across many teams. If you have four engineers, you're buying the costs and none of the benefits.

What actually accumulated

Not everything oscillates. A few things went one direction and stayed there, and they're the genuine progress in this history.

Version control and code review. Automated testing and continuous integration. Containers as a packaging format. Managed databases, which removed an entire category of operational work. TLS everywhere, now the default rather than an upgrade. Structured logging, metrics, and tracing as standard equipment. Infrastructure defined as code rather than configured by hand.

None of these reversed, and collectively they changed the job more than any architectural fashion did. Someone building a backend today gets a vastly better operational baseline than someone in 2005, no matter what shape they deploy it in.

What to take from it

Judge architecture by the problem it solves, not by its recency. Every pattern here was correct for its constraints, and most stop being correct when the constraints change.

Default to fewer moving parts. You can split a modular monolith later. Recombining forty services is considerably harder.

Learn the layer that doesn't move. HTTP, SQL, the request lifecycle, concurrency models, and how to read a trace. That knowledge survived every swing in this history and will survive the next one, which is the same argument I made for the ordering in the server-side tools piece.

Expect the pendulum to keep moving. The current consolidation will accumulate its own costs, and something will eventually make dispersal attractive again. Cheaper distributed primitives, better tooling, something not yet visible.

The job underneath hasn't changed since CGI: take a request, do some work, return a response, and don't fall over when many people ask at once. Everything in this article is a different answer to that, shaped by what was expensive at the time.