Client-Server Architecture: The Complete Foundation Guide

Client-Server Architecture: The Complete Foundation Guide
Every web application, mobile app, and API you interact with is built on client-server architecture. Understanding this model deeply — not just conceptually, but mechanically — is the prerequisite for every other architectural pattern. Microservices, event-driven architecture, serverless — all of them are extensions of client-server thinking.
This guide covers the request-response cycle, HTTP as the protocol, statelessness and how sessions work around it, thin vs fat clients, 3-tier architecture, WebSocket connections, and how you scale a simple client-server setup to handle millions of users.
The Core Model
Client-server architecture separates concerns between two roles:
Client: Requests a service and consumes the response. It runs on the user's device. Server: Provides a service in response to client requests. It runs on infrastructure you control.
Client (browser/mobile app) Server (your infrastructure)
│ │
│ 1. HTTP Request │
│ GET /api/users/123 │
│ Host: api.example.com │
│ Authorization: Bearer abc123 │
│─────────────────────────────────────►│
│ │
│ 2. Server processes request │
│ - Validates auth token │
│ - Queries database │
│ - Applies business logic │
│ │
│ 3. HTTP Response │
│ HTTP/1.1 200 OK │
│ Content-Type: application/json │
│ {"id":123,"name":"Alice",...} │
│◄─────────────────────────────────────│This request-response cycle is the fundamental building block of the web.
HTTP: The Language of Client-Server
HTTP (HyperText Transfer Protocol) is the protocol clients and servers use to communicate. Understanding HTTP thoroughly is essential for architecture.
HTTP Methods
| Method | Meaning | Idempotent | Safe |
|---|---|---|---|
| GET | Read a resource | Yes | Yes |
| POST | Create a resource | No | No |
| PUT | Replace a resource completely | Yes | No |
| PATCH | Partially update a resource | No | No |
| DELETE | Remove a resource | Yes | No |
Idempotent means calling the same request multiple times produces the same result as calling it once. This is critical for retry logic — safe to retry GET and PUT, dangerous to retry POST without an idempotency key.
Safe means the request has no side effects — reading data, not changing it.
HTTP Status Codes
2xx — Success
200 OK — Request succeeded
201 Created — Resource was created
204 No Content — Success, no body (used for DELETE)
3xx — Redirection
301 Moved Permanently — Resource has a new permanent URL
302 Found — Temporary redirect
304 Not Modified — Client's cached version is still valid
4xx — Client Errors
400 Bad Request — Malformed request syntax
401 Unauthorized — Authentication required
403 Forbidden — Authenticated but not authorized
404 Not Found — Resource does not exist
409 Conflict — Conflict with current state (duplicate create)
422 Unprocessable Entity — Validation error
429 Too Many Requests — Rate limit exceeded
5xx — Server Errors
500 Internal Server Error — Unexpected server failure
502 Bad Gateway — Upstream server returned invalid response
503 Service Unavailable — Server overloaded or down for maintenance
504 Gateway Timeout — Upstream server timed outHTTP Headers
Key headers every developer must understand:
# Request headers
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9... # Auth token
Content-Type: application/json # Body format
Accept: application/json # Expected response format
Cache-Control: no-cache # Don't use cached version
If-None-Match: "abc123" # ETag for conditional request
X-Request-ID: uuid-here # Distributed tracing
# Response headers
Content-Type: application/json; charset=utf-8
Cache-Control: public, max-age=3600 # Cache for 1 hour
ETag: "def456" # Version identifier
Location: /api/users/456 # Where the new resource is (201)
X-RateLimit-Remaining: 99 # Rate limit infoStatelessness: The Architectural Constraint That Enables Scale
HTTP is a stateless protocol. Each request is completely independent — the server has no memory of previous requests. This is not a limitation; it is the property that makes web scale possible.
Why statelessness enables scale:
With stateful servers, every user must return to the same server that holds their session:
User connects to Server A → Server A holds session in memory
User's next request must go to Server A → cannot load-balance freelyIf Server A crashes, that user's session is lost. You cannot add servers without sticky session routing. Every server must handle all its own session state.
With stateless servers:
Request 1 → Server A (carries auth token in header)
Request 2 → Server B (carries same auth token)
Request 3 → Server C (carries same auth token)Any server can handle any request because the client carries all the state the server needs to authenticate and serve the request. Adding more servers is trivial.
How Sessions Work in a Stateless World
The client proves its identity on every request using a token:
JWT (JSON Web Token):
Header.Payload.Signature
eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiIxMjMiLCJleHAiOjE3MTM0MzY4MDB9.abc123The payload contains:
{
"userId": "123",
"email": "alice@example.com",
"role": "admin",
"exp": 1713436800 // Expiry timestamp
}The server validates the signature on every request — no database lookup required. All user context is in the token itself.
Server-side sessions (Redis):
Client sends: Cookie: session_id=abc123
Server: redis.get("session:abc123") → { userId: 123, cart: [...] }Session data lives in Redis (not in server memory), so any server can look it up. Stateless from the server's perspective; state lives in the shared Redis store.
Thin vs Fat Clients
The balance of work between client and server has shifted dramatically over time.
Thin Client (Server-Rendered)
The server does almost everything. The client receives complete HTML pages and renders them.
1. User clicks "View Profile"
2. Browser sends: GET /profile/123
3. Server queries DB, builds complete HTML: <html><body>...</body></html>
4. Browser renders the HTMLExamples: Traditional PHP/Rails/Django applications, email clients, internal enterprise tools.
Advantages:
- Simple client requirements (any browser works)
- SEO-friendly (HTML is directly crawlable)
- Secure (business logic never runs on client)
- State is centralized on the server
Disadvantages:
- Every interaction requires a server round-trip
- Full page reloads feel slow
- Server handles all rendering load
Fat Client (Client-Rendered / SPA)
The client does most of the UI work. The server provides data via APIs.
1. Browser downloads JavaScript app once (React, Vue, Angular)
2. User clicks "View Profile"
3. App sends: GET /api/users/123
4. Server returns JSON: {"id":123,"name":"Alice",...}
5. JavaScript renders the UI from the JSON — no page reloadExamples: Gmail, Figma, VS Code for Web, modern web apps.
Advantages:
- Near-instant interactions (no page reloads)
- Server only handles data, not rendering
- Desktop-like user experience
- Client and server teams can work independently
Disadvantages:
- Initial load time (must download the entire JS app)
- SEO requires SSR or static generation
- Business logic exposed to the client (validation must be on server too)
- Complex state management
The 2026 Hybrid: Server Components
Frameworks like Next.js 15 and React Server Components give you both: server renders the initial page (thin client advantage: SEO, fast first load) while the client handles subsequent interactions (fat client advantage: fast navigation).
// Server Component — runs on the server, never sends JS to client
async function UserProfile({ userId }: { userId: string }) {
const user = await db.query('SELECT * FROM users WHERE id = $1', [userId]);
return <div>{user.name}</div>; // Rendered HTML, not JavaScript
}
// Client Component — runs in the browser
'use client';
function LikeButton({ postId }: { postId: string }) {
const [liked, setLiked] = useState(false);
return <button onClick={() => setLiked(!liked)}>{liked ? 'â¤ï¸' : 'ðŸ¤'}</button>;
}3-Tier Architecture
Professional applications split the "server" into three distinct tiers:
Tier 1 (Presentation): Web server / API gateway
Handles HTTP, authentication, rate limiting, routing
Can be scaled independently
Tier 2 (Application): Application servers
Business logic, data processing, orchestration
Can be scaled independently based on CPU/memory needs
Tier 3 (Data): Database, cache, object storage
Persistent data, session storage, file storage
Scaled based on storage and I/O needsEach tier can be scaled independently:
- High web traffic: Add more Tier 1 nodes
- CPU-intensive business logic: Add more Tier 2 nodes
- Database bottleneck: Add read replicas, upgrade hardware, or shard
┌─────────────────────────────â”
Users ─────► │ Load Balancer │
└──────────┬──────────────────┘
│
┌────────────────┼────────────────â”
â–¼ â–¼ â–¼
┌──────────────┠┌──────────────┠┌──────────────â”
│ Web Server │ │ Web Server │ │ Web Server │ Tier 1
│ (NGINX) │ │ (NGINX) │ │ (NGINX) │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
└────────────────┼────────────────┘
│
┌──────────────┼──────────────â”
â–¼ â–¼ â–¼
┌──────────────┠┌──────────────┠┌──────────────â”
│ App Server │ │ App Server │ │ App Server │ Tier 2
│ (Node.js) │ │ (Node.js) │ │ (Node.js) │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
└────────────────┼────────────────┘
│
┌──────────────┼──────────────â”
â–¼ â–¼ â–¼
┌──────────────┠┌──────────────┠┌──────────────â”
│ PostgreSQL │ │ Redis │ │ S3 │ Tier 3
│ (primary) │ │ (cache) │ │ (storage) │
└──────────────┘ └──────────────┘ └──────────────┘REST API Design Principles
REST (Representational State Transfer) is the dominant client-server communication style for web APIs.
Resource-Oriented URLs
# Good: nouns, hierarchical, resource-focused
GET /api/v1/users # List users
POST /api/v1/users # Create user
GET /api/v1/users/123 # Get user 123
PUT /api/v1/users/123 # Update user 123
DELETE /api/v1/users/123 # Delete user 123
GET /api/v1/users/123/orders # Orders belonging to user 123
# Bad: verbs in URLs (RPC-style)
POST /api/getUser
POST /api/createOrder
POST /api/deleteUser?id=123Pagination, Filtering, and Sorting
GET /api/v1/products?page=2&limit=20&sort=-price&category=electronics&minPrice=100
Response:
{
"data": [...],
"pagination": {
"page": 2,
"limit": 20,
"total": 847,
"totalPages": 43
}
}Versioning
# URL versioning (most common)
/api/v1/users
/api/v2/users
# Header versioning
Accept: application/vnd.api+json; version=2WebSockets: Beyond Request-Response
REST is request-response: the client asks, the server answers, the connection closes. Some use cases need persistent, bidirectional communication:
- Real-time chat
- Live dashboards (stock prices, sports scores)
- Collaborative editing (Google Docs)
- Gaming
WebSockets maintain an open connection where either side can send messages at any time:
// Client (browser)
const ws = new WebSocket('wss://api.example.com/ws');
ws.onopen = () => {
ws.send(JSON.stringify({ type: 'subscribe', channel: 'price:BTC' }));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'price_update') {
updatePriceDisplay(data.price);
}
};
// Server (Node.js with ws library)
import { WebSocketServer } from 'ws';
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', (ws) => {
ws.on('message', (data) => {
const message = JSON.parse(data.toString());
if (message.type === 'subscribe') {
subscribeToChannel(ws, message.channel);
}
});
ws.on('close', () => {
unsubscribeFromAllChannels(ws);
});
});WebSocket connections are stateful — they maintain connection state on the server. This creates load balancing challenges (sticky sessions needed, or use a pub/sub broker like Redis for cross-server message delivery).
Scaling Client-Server Architecture
Vertical Scaling (Scale Up)
Add more CPU, RAM, or faster disks to the existing server. Simple but has a hard ceiling — there is a maximum machine size.
Horizontal Scaling (Scale Out)
Add more server instances behind a load balancer. The foundation of web scale:
Before scaling:
Client → Server (1 instance, 10,000 req/day max)
After horizontal scaling:
Client → Load Balancer → Server 1 (10,000 req/day)
→ Server 2 (10,000 req/day)
→ Server 3 (10,000 req/day)
Total: 30,000 req/day, easily extendedPrerequisite: The server must be stateless (no in-memory session state). Store session state in Redis.
Database Scaling
The database is usually the first bottleneck when scaling horizontally:
- Connection pooling: PgBouncer or RDS Proxy to reduce connection overhead
- Read replicas: Route read-heavy queries to replicas, writes to primary
- Caching: Redis cache for expensive query results
- Sharding: Split data across multiple database instances (by user ID range, geography, etc.)
Frequently Asked Questions
Q: What is the difference between client-server and peer-to-peer architecture?
In client-server, there is a clear asymmetry: the server provides a service and the client consumes it. The server is shared infrastructure. In peer-to-peer (P2P), every node is both a client and a server — it consumes services from other nodes and provides services to others. P2P is used in BitTorrent (file sharing), Bitcoin (distributed ledger), and WebRTC (browser-to-browser communication). Client-server gives you more control; P2P gives you resilience against single-server failures.
Q: Why can't the server just remember who I am between requests?
It can, but storing state in server memory prevents horizontal scaling. If your session is stored in Server A's memory, your next request must go to Server A — you cannot send it to Server B or C. Stateless architecture solves this: the client proves its identity on every request via a token, and any server can handle any request.
Q: What is the difference between REST and GraphQL?
REST uses multiple endpoints, each representing a resource. GraphQL uses a single endpoint and lets the client specify exactly what data it needs. REST is simpler and cacheable; GraphQL is more flexible for complex data requirements and avoids over-fetching. For most APIs, REST is the right choice. GraphQL adds value when clients have very different data needs (mobile vs web) or when reducing network requests is critical.
Key Takeaway
Client-server architecture is the universal pattern of the internet: a client sends a request, a server processes it and responds. The two key architectural constraints that make this scalable are statelessness (any server can handle any request because state travels with the request) and separation of concerns (the client handles presentation, the server handles data and business logic). Everything else — microservices, serverless, event-driven architecture — is built on top of this foundation. Master the request-response cycle, HTTP, REST design, and the 3-tier model before moving to more complex patterns.
Read next: Edge Computing: Bringing the Code to the User →
Part of the Software Architecture Hub — engineering the foundation.
