Warming up the neural circuits...
By the end of this chapter you will:
This chapter is the map of L0. Every chapter ahead drills into one of the boxes you'll see here. If you can draw this diagram from memory, you have the architecture of every web service ever built.
A car factory turns raw steel into a finished car. The path is fixed: stamping → welding → paint → assembly → quality check → shipping. A worker only needs to know their station — they don't need to know the whole plant.
A backend is the same. An HTTP request travels a fixed path: edge → router → → handler → service → database → and back. Each station does one thing. You can debug any production issue by asking "which station broke?"
Here's the whole journey, laid flat:
Rendering diagram…
Let's walk through, station by station.
The user types or clicks. The browser builds an HTTP request, picks a connection (or opens a new one), and ships it. It does NOT know what server it's actually talking to — it knows a hostname.
Turns the hostname into an IP. Cached at every layer (browser, OS, ISP). When you "switch servers", DNS is what you change.
For most assets and many APIs, the request never reaches your code. A node geographically close to the user has a cached copy and responds in 20ms. You set the rules with Cache-Control headers.
Picks one of your N app servers (round-robin, least-conn, or by hash). Also does TLS termination, health checks, and sometimes WAF (web app firewall).
Lives next to your app. Adds headers (X-Forwarded-For), serves static files, gzips responses, handles slow clients without tying up your Node process.
This is where your code lives. The request flows through:
Routing → Middleware chain → Controller → Service → Repository → DBEach step does one thing:
The slowest, most precious thing. Reads might hit Redis first. Writes go to Postgres. Slow work goes into a (BullMQ) for a background worker to handle later.
The response retraces every step in reverse. nginx gzips. The CDN may cache (if your Cache-Control says so). The browser parses and renders.
A common confusion: "where do I put ?" or "where does auth go?" Here's the cheat sheet:
| Concern | Lives at | Why |
|---|---|---|
| TLS termination | One cert, no per-app config | |
| DDoS protection | CDN / WAF | Cheap to block at the edge |
| Static file serving | CDN or nginx | Don't waste Node cycles |
| Rate limiting (per-IP) | Edge / nginx | Block before Node sees it |
| Rate limiting (per-user) | App middleware | Need to know who they are |
| Authentication | App middleware | Knows your user model |
| Authorization | App service layer | Knows your business rules |
| Request validation | App middleware (Zod) | Reject malformed inputs fast |
| Business logic | App service | The whole point |
| Persistent data | Database | Postgres |
| Hot keys / cache | Redis | Speed |
| Slow / work | Queue + worker | Don't tie up the request |
If you put auth at the wrong layer, you'll have weird bugs. If you put rate limiting in the wrong place, attackers will eat your DB.
Eventually one of these will be slow. Knowing which one — fast — separates seniors from mids.
Each has a different fix:
| Bottleneck | Symptom | Fix |
|---|---|---|
| CPU | High CPU%, slow response on simple URLs | Profile, optimize, or move to a worker thread |
| I/O wait | Low CPU%, high latency | Cache, batch, parallelize |
| Memory | OOM kills, GC pauses | Stream instead of buffer, find the leak |
| Concurrency | "Too many connections" errors | Pool the upstream, queue overflow, or scale out |
Always measure before guessing. Top, htop, and your APM's flamegraph are your friends.
A request can fail at any station. Symptoms tell you which:
| Symptom | Likely station |
|---|---|
ERR_NAME_NOT_RESOLVED | DNS |
ERR_CONNECTION_REFUSED | Load balancer / firewall |
ERR_CERT_AUTHORITY_INVALID | TLS / cert |
| 502 from nginx | Origin app crashed |
503 with Retry-After | Origin overloaded |
| 504 from nginx | Origin too slow |
| 200 but wrong body | Bug in your handler |
| Random 5xx that "fixes itself" | Maybe a DB connection pool issue |
| Slow specific endpoint | Probably the database query |
This table is more useful than 100 Overflow answers.
Some work belongs in the request cycle. Some doesn't.
Synchronous (inside the request):
Asynchronous (background queue):
Rule of thumb: if it doesn't change what the user sees in the next 200ms, put it in a queue.
| Mistake | Why it's wrong | What to do |
|---|---|---|
| Doing email send inside POST /signup | One slow SMTP = slow signup | Enqueue it; return immediately |
| Caching personalized data at the CDN | Users see each other's data | Mark Cache-Control: private |
| Auth at the controller layer | Easy to forget on a new endpoint | Auth middleware applied at the router |
| Logging the request body unfiltered | PII / passwords in logs | Redact known sensitive fields |
| No timeout on outbound calls | One slow upstream stalls every worker | Set a connect + read timeout |
Production. Give every request an ID. Pass it through logs and downstream calls. When something breaks, you can trace one user's journey.
Performance. Profile early. The bottleneck is rarely where you think.
Security. Validate at the edge. Reject malformed before Node parses it. Authentication early, authorization at the boundary where it matters.
Beginner. What happens between the user typing a URL and the server seeing the request? Walk through DNS, TCP, TLS, edge/CDN, LB, reverse proxy.
Senior. A specific endpoint is slow. Walk me through how you'd diagnose it. APM/timing → which layer is slow? DB query? Outbound call? CPU? Then you fix the specific layer.
A request travels a fixed path: browser → DNS → CDN → LB → reverse proxy → app → DB/cache/queue → back. Each station does one job. Each concern (auth, rate limit, cache, TLS) has a home — put it in the right one. When something breaks, ask "which station?" and you'll find it fast. This is the map; the of the course fills in details.