FoundationsGENERALRUNTIME-SPECIFIC

What a Backend Actually Is

A long-lived process holding the things a client cannot be trusted with, reached over a protocol it does not control.

What actually happensHow to build it

The requirement, the obvious build, and why it breaks

Every lesson starts where the work starts: someone asked for something, and the first implementation that comes to mind has a problem.

The question

If frameworks disappeared tomorrow, what would still be true about a backend?

The requirement

We have a product. Some of it has to run somewhere other than the user's device. Which parts, and why those?

The obvious build

A backend is the server-side framework — the thing you npm install that gives you app.get() and a database connection. Backend work is learning its conventions.

Why it breaks

The framework decides your routing syntax; it does not decide what happens when the database is slow, and that is what pages you at 3am.

How it breaks in production
  • The framework decides your routing syntax; it does not decide what happens when the database is slow, and that is what pages you at 3am.
  • Two services built on the same framework fail in completely different ways depending on their runtime model, pool sizes and dependency graph — none of which the framework documents.
  • Every framework hides HTTP parsing, connection reuse and serialization. When one of those becomes your bottleneck, the framework has no vocabulary for the problem.
  • Framework knowledge does not transfer. The engineering underneath it transfers to every framework, language and decade.
RequirementAPI ContractApplication LogicData AccessExternal DepsConcurrencyFailureSecurityObservabilityDeploymentScale

What is actually happening

  • A backend is a process that outlives any single request. That is the whole foundation: state can persist between requests, connections can be reused, caches can be warm, and work can continue after a response is sent.
  • It listens on a socket. Something outside — a browser, a mobile app, another service, a webhook from a payment provider — sends bytes it did not write and cannot be trusted.
  • It holds authority. It owns the credentials to the database, the API keys to third parties, the right to decide whether this caller may see that row. The client holds none of these.
  • It is shared. Many callers hit the same process concurrently, so anything mutable is a potential race and anything finite (memory, connections, threads) is a potential queue.
  • It outlives failures it does not cause. The database restarts, the network blips, a dependency returns 500 — the backend is still expected to behave sensibly.

The process is the unit, not the request

Frontend code is born and dies with a page. Backend code does neither. A backend process starts, opens connections, warms caches, and then serves thousands of requests that know nothing about each other — before being terminated by a deploy rather than by finishing its work.

Almost everything distinctive about backend engineering follows from that. Connection pools exist because reconnecting per request is wasteful when the process persists. Caches are possible for the same reason. Races exist because requests overlap inside one process. Graceful shutdown exists because the process ends at a moment unrelated to any request's progress.

  • Persistent — state survives between requests, for better and worse.
  • Listening — it accepts input from parties it does not control.
  • Authoritative — it holds the credentials and makes the decisions.
  • Shared — concurrent requests contend for the same finite resources.
  • Exposed to others' failure — dependencies break on their schedule, not yours.
requestconcurrent requestshared, finitemay never answerClient AClient BOne long-lived processConnection poolPayment APIDatabase
UserLLMAgentToolDataDecisionHumanGuardrail

What only the server can do

The useful test for "does this belong in the backend" is not "is it complicated". It is: can the client be trusted with it, and can the client be relied upon to do it? Those are two different questions, and both send work server-side.

A price calculation on the client is not wrong because clients are slow. It is wrong because a client can lie about the result. Meanwhile, sending a receipt email from the client is not a trust problem — it is a reliability problem, because the browser can close mid-request.

Where does this logic belong?

Why does this need to run on the server?

Trust — the client could lie

when Pricing, permissions, balances, anything a user benefits from falsifying.

cost A round trip, and latency the user feels.

Authority — it needs a secret

when Calls to third parties, database writes, signing.

cost You now own key rotation and blast radius.

Reliability — it must happen even if the client vanishes

when Emails, webhooks, order fulfilment.

cost It becomes a background job with its own failure modes (Background Jobs).

Shared truth — everyone must see the same value

when Inventory, counters, sequence numbers.

cost Contention on a shared resource (Backend Races).

None of these

when Formatting, layout, optimistic UI, input hints.

cost Putting it on the server costs a round trip and buys nothing.

The framework is one layer, and not the interesting one

Frameworks differ in routing syntax, middleware signature and configuration style. They agree almost completely on what actually happens: a socket is accepted, bytes are parsed into a request, a handler runs, a response is serialized, bytes are written. The parts that differ are the parts you can look up.

This matters practically. When your p99 triples, the answer is never in the routing DSL. It is in a pool that has 20 connections and 200 waiters, a JSON payload that grew, an external call with no timeout, or CPU work on a thread that cannot yield. None of those are framework questions, and a framework-shaped mental model has nowhere to put them.

Two ways to read the same endpoint
Framework-shaped
app.post('/orders', createOrder)
// "a POST route bound to a handler"
Engineering-shaped
app.post('/orders', createOrder)
// untrusted bytes -> parse -> authenticate -> authorize
// -> validate -> transaction -> external call -> event
// -> serialize -> log/metric/trace
// what can fail? what can race? what must be atomic?

The second reading tells you where to put a timeout, which step needs the transaction, and what happens on a retry. The first tells you the URL.

How to build it

Most important first.

  • Learn the request lifecycle before any framework's router: what happens between a socket accept and a byte written back (The Request Lifecycle).
  • Decide what belongs on the server because it *must* — authority, shared state, secrets — rather than because it is conventional (What the Backend Is Responsible For).
  • Treat everything crossing the process boundary as untrusted, including responses from services you own (The Trust Boundary).
  • Know your runtime's concurrency model, because it determines what "slow" does to everything else in flight (Backend Runtime Models).

What can go wrong

Failure modes
  • Logic that assumes a single instance — an in-memory counter, a local lock, a cached session — silently breaks the moment a second instance is deployed (Stateless Services).
  • Trusting a field the client sent because it was populated by your own frontend. The frontend is not the only client.
  • Assuming a dependency returns. Everything outside the process can hang (Timeouts).
What can race
  • Anything the process holds in memory is shared by every concurrent request in that process. A module-level variable is shared mutable state (Backend Races).
Security
  • The backend is the only place authorization can be enforced. A hidden button is a UI affordance, not a control (Where the Check Belongs).
  • Secrets live server-side because the client is fully inspectable. Anything shipped to a browser is public, including values in a bundled config object.
  • Every input from outside the process is attacker-controlled until validated — body, query, headers, cookies, uploaded files and webhook payloads alike.
Misreads
  • "The backend is the database layer." The database is a dependency. A backend with no database is still a backend.
  • "Backend means REST API." Queue workers, schedulers and stream consumers have no HTTP surface and are backend engineering in full.
  • "If I know the framework, I know the backend." The framework is one implementation of routing, parsing and lifecycle. It is the least transferable part of what you know.

Operating it

How you see it in production
  • A request log with method, path, status, duration and a correlation id is the minimum that makes a backend explainable at all (Correlation Ids That Survive Every Hop).
  • Process-level metrics — memory, event-loop lag or thread-pool saturation, open connections — tell you about the *process*, which request logs cannot.
What changes at 10x and 100x
  • One process becomes many. Every assumption about local state becomes a bug, and the ones that do not crash are the dangerous ones.
  • Finite resources start queueing before they start failing: the pool, the loop, the worker set. Latency rises long before errors do.
What this costs
  • Putting logic server-side costs a network round trip and a scaling problem you now own. Some work genuinely belongs on the client — it is just never the work that decides trust.
  • Learning the layer beneath the framework is slower to start. It pays back the first time production does something the documentation does not describe.

Where this applies

Backend advice is context-sensitive. These labels say what each claim is specific to, and where a different stack or scale would differ.

  • GENERALTrue of backends across languages, frameworks and decades. The properties come from the process/socket model, not from any stack.
  • RUNTIME-SPECIFICWhat "shared" means differs: Node shares one loop thread per process, so module state is shared across all in-flight requests; a pre-fork Python or PHP model gives each worker its own memory, so the same code has per-worker state instead. Both surprise people, in opposite directions.

Where the depth lives

This domain teaches the application-side mechanism and hands the rest off.

Architecturemonolith
Performancetail-latency
Domains that do not exist yet
  • Programming Languages & Runtime Internals — how source becomes a running process, and what the runtime does between your lines.