In the previous chapters, we followed a request from the user all the way to HTTP communication:
User
↓
Browser / Mobile App
↓
DNS
↓
IP
↓
TCP / QUIC
↓
TLS
↓
HTTP
↓
Load Balancer / Reverse Proxy
↓
ApplicationNow we need to understand the application side of that architecture.
When an HTTP request reaches a server:
What actually happens?
What is a server?
What is a process?
What is a thread?
What is a worker?
What is a web server?
What is an application server?
How can one machine handle thousands of requests?
How do we make an application horizontally scalable?
These questions are fundamental to system design.
The word server can mean different things depending on context.
At the simplest level:
A server is a computer or software process that provides a service to clients.
For example:
Browser
↓
Web Serveror:
Application
↓
Database Serveror:
Application
↓
Redis ServerThe word "server" can therefore refer to:
This distinction is important.
A server machine is simply a computer running workloads.
It has resources such as:
CPU
RAM
Disk
NetworkFor example:
Server
├── 8 CPU cores
├── 32 GB RAM
├── 500 GB SSD
└── 1 Gbps networkA cloud VM is also essentially a virtualized server environment.
Examples of server environments include:
A server program listens for requests.
For example:
Application starts
↓
Bind to port 8080
↓
Listen for connections
↓
Receive request
↓
Process request
↓
Send responseIn Node.js:
import http from "http";
const server = http.createServer((req, res) => {
res.end("Hello");
});
server.listen(3000);The important concept is:
listen → receive → process → respondA network service normally listens on a port.
Example:
192.168.1.10:3000Here:
192.168.1.10 → IP address
3000 → portA server can run multiple services:
:80 → HTTP
:443 → HTTPS
:5432 → PostgreSQL
:6379 → RedisThe operating system uses ports to direct network traffic to the appropriate process.
A simplified flow:
Network
↓
Operating System
↓
Listening Socket
↓
Server Process
↓
Request Handler
↓
Business Logic
↓
Database / Cache / Other Services
↓
ResponseThis is a key system-design mental model.
A socket is an endpoint used for network communication.
Conceptually:
Client Socket
↕
Network
↕
Server SocketA server can listen on a socket such as:
0.0.0.0:8080When connections arrive, the operating system manages the underlying network state and makes data available to the server process.
A process is a running instance of a program.
For example:
Node.js application
↓
ProcessA process has its own:
Conceptually:
Process
├── Code
├── Heap
├── Stack
├── Open files
├── Sockets
└── Other resourcesA thread is an execution path within a process.
A process can have:
Process
├── Thread 1
├── Thread 2
├── Thread 3
└── Thread 4Threads within the same process can share process memory.
This makes communication between threads efficient, but shared memory also creates concurrency challenges.
Examples:
| Process | Thread |
|---|---|
| Separate process memory | Shares process memory |
| Heavier isolation | Lighter execution unit |
| More expensive to create | Usually cheaper to create |
| Process failure can isolate resources | Thread failure can affect process |
| IPC needed between processes | Shared memory can be used |
This is a simplified model; actual behavior depends on the operating system and runtime.
Suppose a machine has:
8 CPU coresIt can execute multiple threads concurrently.
A useful mental model:
CPU
├── Core 1
├── Core 2
├── Core 3
├── Core 4
├── Core 5
├── Core 6
├── Core 7
└── Core 8Operating systems schedule runnable threads onto CPU cores.
Important distinction:
A program can have many threads even when the machine has fewer CPU cores.
The OS schedules them over time.
These are often confused.
Multiple tasks are in progress during overlapping periods.
Task A ───────
Task B ───────
Task C ───────Multiple tasks actually execute simultaneously on different CPU cores.
Core 1 → Task A
Core 2 → Task B
Core 3 → Task CA system can be concurrent without everything executing simultaneously.
A web server is software that handles HTTP-related communication, especially serving web content and forwarding requests.
Examples include:
A web server can handle:
Example:
Browser
↓
Nginx
├── /images → static files
└── /api → application serverAn application server runs application/business logic.
For example:
HTTP Request
↓
Application Server
↓
Validate request
↓
Authenticate user
↓
Business logic
↓
Database
↓
ResponseIn modern frameworks, the distinction between web server and application server can be blurred.
For example, a framework may include its own HTTP server while Nginx sits in front as a reverse proxy.
A simplified distinction:
| Web Server | Application Server |
|---|---|
| Static content | Business logic |
| Reverse proxy | Application processing |
| TLS termination | Database interaction |
| HTTP connection handling | Authentication/authorization |
| Caching | Domain logic |
In production, one component can perform multiple roles.
Do not treat the terminology as an absolute architectural boundary.
A reverse proxy receives client requests and forwards them to backend services.
Client
↓
Reverse Proxy
↓
ApplicationExamples:
A reverse proxy can provide:
Suppose:
Internet
↓
Application ServerNow imagine adding:
Internet
↓
Reverse Proxy
↓
Application ServerThe proxy can absorb infrastructure concerns.
For example:
Client
↓ HTTPS
Nginx
↓ HTTP/internal TLS
AppThe application may no longer need to directly manage all public-facing TLS and connection behavior.
Suppose we have:
Node.js applicationStarting it creates a process.
Machine
└── Node Process
└── ApplicationThat process consumes:
CPU
RAM
File descriptors
Network socketsAs traffic increases, these resources can become bottlenecks.
A simplistic architecture:
Client 1 ─┐
Client 2 ─┤
Client 3 ─┼──→ Application Process
Client 4 ─┤
Client 5 ─┘The application receives requests and schedules their processing according to its runtime model.
Different languages/runtimes handle concurrency differently.
Examples:
System design should focus on the resource and concurrency model rather than assuming all runtimes behave identically.
Node.js uses an event-driven architecture.
A simplified model:
Incoming Request
↓
Event Loop
↓
Application Code
↓
Async I/O
↓
Event Loop
↓
ResponseFor I/O-heavy workloads, Node.js can handle many concurrent operations without creating one OS thread per request.
However:
JavaScript execution on the main Node.js event-loop thread is not magically parallel.
CPU-heavy work can block the event loop.
Suppose:
app.get("/report", (req, res) => {
// Very expensive CPU calculation
});If this blocks the event loop:
Request A
↓
CPU-heavy work
↓
Event loop blocked
Request B → waits
Request C → waits
Request D → waitsTherefore CPU-intensive work may need:
A worker is a unit responsible for processing work.
For example:
API Server
↓
Queue
↓
Workers
├── Worker 1
├── Worker 2
├── Worker 3
└── Worker 4Workers are especially useful for background tasks:
Instead of creating a new worker for every task, a system can maintain a pool.
Worker Pool
├── Worker 1
├── Worker 2
├── Worker 3
└── Worker 4Jobs are distributed among available workers.
Benefits:
Database connections are expensive resources.
Instead of:
Request
↓
Create DB connection
↓
Query
↓
Close connectionfor every request, applications commonly use a connection pool.
Application
↓
Connection Pool
├── DB Connection 1
├── DB Connection 2
├── DB Connection 3
└── DB Connection 4
↓
DatabaseRequests reuse connections.
Suppose:
1,000 concurrent requestsand every request creates a new database connection.
The database may become overwhelmed.
Instead:
1,000 application requests
↓
Connection Pool
↓
50 database connections
↓
DatabaseThe exact pool size depends on workload and database capacity.
A pool is therefore a form of resource throttling.
Suppose:
Pool size = 20
Active queries = 20Request #21 may have to wait.
If waiting requests continue to accumulate:
Requests
↓
Connection Pool
↓
Waiting Queue
↓
TimeoutsThis can create cascading latency.
Therefore connection pool configuration is a system-design concern.
Many runtimes maintain worker threads for operations such as:
The exact implementation differs by runtime.
The general concept is:
Application
↓
Thread Pool
├── Thread 1
├── Thread 2
├── Thread 3
└── Thread 4If all workers are busy, additional work waits.
Let's follow a request.
HTTP Request
↓
Reverse Proxy
↓
Application Server
↓
Router
↓
Middleware
↓
Authentication
↓
Validation
↓
Controller / Handler
↓
Business Logic
↓
Cache / Database / External API
↓
Response Serialization
↓
HTTP ResponseThis is one of the most useful mental models for backend system design.
Routing determines which application code handles a request.
Example:
GET /users/123may map to:
UserController.getUser(123)Another:
POST /ordersmay map to:
OrderController.createOrder()Middleware runs during request processing.
Example:
Request
↓
Logging middleware
↓
Authentication middleware
↓
Validation middleware
↓
Route handler
↓
ResponseMiddleware can perform:
Business logic contains the actual application rules.
For example:
Create Order
↓
Check user
↓
Check inventory
↓
Calculate price
↓
Apply discount
↓
Create order
↓
Publish eventDo not put all business logic directly into HTTP handlers.
A cleaner architecture separates:
HTTP Layer
↓
Application / Domain Layer
↓
Data Access LayerA common structure:
┌──────────────────────────┐
│ Presentation / API Layer │
├──────────────────────────┤
│ Business / Service Layer │
├──────────────────────────┤
│ Data Access Layer │
├──────────────────────────┤
│ Database │
└──────────────────────────┘Example:
Controller
↓
Service
↓
Repository
↓
DatabaseThis separation makes code easier to test and evolve.
The controller handles the HTTP-facing concerns.
Example:
POST /usersController responsibilities may include:
It should not necessarily contain every business rule.
The service layer implements application behavior.
Example:
UserService.createUser()It may:
validate business rules
↓
check duplicate email
↓
hash password
↓
create user
↓
publish eventThe repository handles persistence concerns.
Example:
UserRepository.findByEmail()
UserRepository.create()
UserRepository.findById()The service should not need to know every SQL implementation detail.
Conceptually:
Controller
↓
Service
↓
Repository
↓
DatabaseA monolith is an application deployed as a single application unit.
Example:
Monolith
┌──────────────────────────────────┐
│ Users │
│ Orders │
│ Payments │
│ Notifications │
│ Admin │
└──────────────────────────────────┘
↓
DatabaseAll major application functionality is packaged and deployed together.
A modular monolith keeps one deployment unit but separates internal domains.
Monolith
├── User Module
├── Order Module
├── Payment Module
├── Notification Module
└── Admin ModuleThis can provide stronger internal organization without immediately introducing distributed-system complexity.
In a microservice architecture:
User Service
Order Service
Payment Service
Notification ServiceEach service can potentially be deployed independently.
API Gateway
↓
┌──────────────┼──────────────┐
↓ ↓ ↓
User Service Order Service Payment Service
↓ ↓ ↓
User DB Order DB Payment DBMicroservices provide flexibility but introduce distributed-system complexity.
| Monolith | Microservices |
|---|---|
| Simpler deployment | Independent deployment |
| Simple local calls | Network communication |
| Easier transactions | Distributed transactions harder |
| Easier debugging initially | Distributed tracing needed |
| Can scale as one unit | Services can scale independently |
| Lower operational complexity | Higher operational complexity |
There is no universal rule that one architecture is always correct.
The architecture should match the problem.
Suppose your application has:
5 users
3 developers
simple CRUDCreating:
15 microservices
20 databases
Kafka
Service mesh
API gatewaycan add enormous operational complexity without solving a real problem.
A well-structured monolith may be sufficient.
A common evolution path is:
Modular Monolith
↓
Identify scaling/team/domain boundaries
↓
Extract selected services
↓
Microservices where justifiedSuppose one application instance can handle:
1,000 requests/secTraffic becomes:
5,000 requests/secInstead of making one machine five times larger:
Machine A
5× CPUwe can run:
Server 1
Server 2
Server 3
Server 4
Server 5and distribute requests.
Load Balancer
/ | | \
↓ ↓ ↓ ↓
App1 App2 App3 App4This is horizontal scaling.
Instead, we can increase the resources of one server.
Before:
4 CPU
16 GB RAM
After:
16 CPU
64 GB RAMThis is vertical scaling.
It is often simpler initially, but there are hardware/resource limits.
Horizontal scaling becomes easier when application instances are stateless.
Instead of:
App1
└── User session in local memoryprefer:
App1 ─┐
App2 ─┼──→ Shared Session Store
App3 ─┘or use stateless authentication when appropriate.
Then any instance can handle the next request.
A typical architecture:
Internet
↓
Load Balancer
/ | \
↓ ↓ ↓
App1 App2 App3
\ | /
\ | /
Cache
↓
DatabaseThe load balancer distributes requests across instances.
We will study load balancing in detail in Chapter 07.
A load balancer should avoid sending traffic to unhealthy instances.
Two common concepts:
Is the process alive?
Is the application ready to receive traffic?
Example:
GET /health/live
GET /health/readyAn application may be alive but not ready.
For example:
Process running
Database connection unavailableThe process is alive, but it may not be ready to serve requests.
Suppose an application instance is being replaced.
Bad shutdown:
Kill process
↓
Active requests failGraceful shutdown:
Stop accepting new requests
↓
Finish active requests
↓
Close connections
↓
Flush important buffers
↓
ExitThis reduces request failures during deployments and scaling events.
Suppose:
Version A
App1
App2
App3Deploy Version B:
App1 → Version B
App2 → Version B
App3 → Version BA load balancer can gradually remove old instances from service.
Conceptually:
Traffic
↓
Old instances + new instances
↓
Gradually shift
↓
Only new instancesLater we will study deployment strategies more deeply.
An application server can be limited by:
CPU = 100%RAM exhaustedNetwork bandwidth saturatedToo many open files/sockets.
All DB connections busy.
A slow dependency causes application requests to wait.
Threads/processes wait for shared resources.
Backpressure means slowing or limiting incoming work when downstream capacity is limited.
Example:
Traffic
↓
API
↓
Queue
↓
WorkersSuppose workers can process:
1,000 jobs/secbut incoming traffic produces:
10,000 jobs/secThe queue grows.
A system needs a strategy:
Without backpressure, queues and memory can grow until the system fails.
Sometimes the correct action during overload is to reject work.
Example:
System capacity = 10,000 req/sec
Incoming = 20,000 req/secTrying to process everything may cause:
CPU exhaustion
memory exhaustion
timeouts
cascading failuresInstead, the system may reject some requests:
429 Too Many Requests
503 Service UnavailableThis protects the remaining capacity.
A system can sometimes provide reduced functionality instead of failing completely.
Example:
Recommendation Service unavailableInstead of:
Entire product → failurethe application can return:
Product page
+
"No recommendations currently available"This is graceful degradation.
A server may use a cache:
Request
↓
Application
↓
Cache hit?
├── Yes → return
└── No
↓
Database
↓
Store cache
↓
ReturnThis reduces database load and latency.
We will study caching in depth in Chapters 15 and 16.
For web applications, the server may generate HTML.
Browser
↓
HTTP Request
↓
Web/Application Server
↓
Generate HTML
↓
ResponseAlternatively, the server may return JSON to a frontend application.
Browser
↓
API
↓
JSON
↓
React / Next.jsThe architecture depends on the application requirements.
A server can serve static content:
logo.png
style.css
app.jsor dynamic content:
GET /profile
↓
Database lookup
↓
Generate responseStatic content is often moved to a CDN or object storage.
Dynamic requests usually require application processing.
A simple production architecture:
Internet
↓
CDN
↓
Load Balancer
↓
┌─────────┼─────────┐
↓ ↓ ↓
App1 App2 App3
│ │ │
└────┬────┴────┬────┘
↓ ↓
Redis DatabaseThe application servers are stateless.
Shared state is stored in external systems.
Internet
│
▼
CDN / Edge
│
▼
Load Balancer
│
▼
API Gateway
│
┌───────────┼───────────┐
▼ ▼ ▼
App 1 App 2 App 3
│ │ │
└───────────┼───────────┘
│
┌──────────┼──────────┐
▼ ▼ ▼
Redis Database Queue
│
┌──────────┼──────────┐
▼ ▼ ▼
Worker 1 Worker 2 Worker 3This architecture combines:
A request can perform work synchronously:
Client
↓
API
↓
Process
↓
Database
↓
ResponseOr move expensive work to a background queue:
Client
↓
API
↓
Queue
↓
202 AcceptedThen:
Queue
↓
Worker
↓
Database / StorageThis allows the API to return quickly.
Good candidates include:
The client does not necessarily need to wait for these operations.
Good application architecture usually aims for:
Different responsibilities remain separated.
Avoid unnecessary local state.
Separate domains and dependencies.
Use connection pools and bounded concurrency.
Do not wait forever.
Logs, metrics, and traces should make failures diagnosable.
Avoid taking down the entire system because one dependency failed.
Do not introduce distributed complexity without a reason.
A server can mean a machine, process, or service.
Different runtimes use different concurrency models.
Too many threads can cause:
Database connections are limited resources.
Horizontal scaling and instance replacement can lose it.
Microservices introduce:
Deployments and scaling events can interrupt active requests.
A load balancer may continue sending traffic to unhealthy instances.
Unlimited incoming work can exhaust resources.
A good answer:
Network stack
↓
Socket
↓
Server process
↓
HTTP parser
↓
Router
↓
Middleware
↓
Authentication/validation
↓
Business logic
↓
Cache/database/dependencies
↓
Response serialization
↓
HTTP responseA process provides an isolated execution environment and memory space.
Threads execute within a process and generally share its memory.
Because any healthy instance can process the next request without requiring locally stored session state.
To reuse expensive database connections and limit the number of concurrent database connections.
Requests may wait for a connection and eventually timeout or fail.
It can centralize TLS termination, routing, load balancing, caching, request policies, and other infrastructure concerns.
The answer depends on requirements.
Consider:
Because the load balancer can send requests to any healthy instance without depending on a particular instance's local state.
Stop accepting new work, finish active work where possible, close resources, and then terminate.
A mechanism for preventing incoming work from overwhelming downstream capacity.
Design a server architecture for a simple e-commerce application.
Requirements:
Start with:
Client
↓
Application
↓
DatabaseThen improve it to:
Client
↓
Load Balancer
↓
Application Servers
↓
Cache
↓
DatabaseThen add:
Queue
↓
Notification WorkersIdentify:
Build a conceptual architecture:
Client
↓
Load Balancer
↓
┌──────┴──────┐
↓ ↓
App 1 App 2
│ │
└──────┬──────┘
↓
Redis
↓
PostgreSQL
↓
Queue
↓
WorkersImplement or document:
You should now understand:
We can now expand our mental model:
INTERNET
│
▼
CDN / EDGE
│
▼
LOAD BALANCER
│
▼
REVERSE PROXY
│
▼
APPLICATION
│
┌─────────────┼─────────────┐
│ │ │
▼ ▼ ▼
Routing Middleware Workers
│ │
└───────┬─────┘
▼
Business Logic
│
┌───────┼────────┐
▼ ▼ ▼
Cache Database Queue
│
▼
WorkersThe critical insight is:
A production application is not simply "a server running code."
It is a collection of processes, resources, dependencies, and control mechanisms that must work together under load and failure.
Before moving forward, make sure you can explain:
Now that we understand what happens inside an application server, we are ready for one of the central topics of System Design:
How do we make an application handle increasing traffic?
We will learn:
1 Server
↓
Vertical Scaling
↓
Multiple Servers
↓
Horizontal Scaling
↓
Load Balancing
↓
Stateless Architecture
↓
Caching
↓
Database Scaling
↓
Async Processing
↓
Distributed ArchitectureWe will cover:
After Chapter 06, we will move into Chapter 07 — Load Balancers, where we will deeply study how traffic is distributed across multiple application servers.
Mark it as complete to track your mastery.