ArchitectureSystem Design

Client-Server Architecture: The Complete Foundation Guide

TT
TopicTrick Team
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.

text
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

MethodMeaningIdempotentSafe
GETRead a resourceYesYes
POSTCreate a resourceNoNo
PUTReplace a resource completelyYesNo
PATCHPartially update a resourceNoNo
DELETERemove a resourceYesNo

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

text
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 out

HTTP Headers

Key headers every developer must understand:

http
# 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 info

Statelessness: 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:

text
User connects to Server A → Server A holds session in memory
User's next request must go to Server A → cannot load-balance freely

If 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:

text
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):

text
Header.Payload.Signature
eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiIxMjMiLCJleHAiOjE3MTM0MzY4MDB9.abc123

The payload contains:

json
{
  "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):

text
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.

text
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 HTML

Examples: 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.

text
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 reload

Examples: 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).

typescript
// 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:

text
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 needs

Each 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
text
                    ┌─────────────────────────────┐
     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

text
# 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=123

Pagination, Filtering, and Sorting

http
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

text
# URL versioning (most common)
/api/v1/users
/api/v2/users

# Header versioning
Accept: application/vnd.api+json; version=2

WebSockets: 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:

javascript
// 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:

text
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 extended

Prerequisite: 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:

  1. Connection pooling: PgBouncer or RDS Proxy to reduce connection overhead
  2. Read replicas: Route read-heavy queries to replicas, writes to primary
  3. Caching: Redis cache for expensive query results
  4. 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.