Client-Server Architecture Explained: Clients, APIs, Servers, Proxies, State, Caching, and Scaling
Client-server architecture separates a system into clients that request capabilities and servers that provide resources or services over a network.
A browser calling an API is the obvious example, but the same pattern appears in mobile apps, desktop software, database drivers, game clients, IoT devices, CLI tools, and service-to-service communication.
The useful part of the model is not simply “two computers.” It is the separation of responsibilities, trust boundaries, and deployment lifecycles between the consumer and the provider.
The client initiates interaction
In ordinary HTTP, the client initiates requests.
A client might be:
- web browser
- mobile app
- desktop application
- CLI
- another backend service
It chooses a target resource or operation and sends a message according to the protocol.
For example:
GET /products/p42
Accept: application/json
The server processes the request and returns a response.
The server owns authoritative capabilities
The server normally owns things clients should not control directly:
- business rules
- authorization
- durable database writes
- secrets
- shared state
- system-of-record data
A frontend may validate an order form for user experience, but the server must validate it again before accepting the order.
Client code is under user control. Hidden buttons and browser-side checks are not security boundaries.
One server can serve many kinds of clients
A backend may expose the same domain to:
web app
mobile app
admin app
partner integration
The exact API can be shared or tailored through BFF/client-specific layers.
This separation lets user interfaces evolve without moving core business rules into every client.
Servers are usually software roles, not one physical machine
A “server” can be:
- one process on one VM
- several containers
- serverless functions
- a globally distributed service
Clients often see one hostname:
api.example.com
while the actual implementation contains many replicas and infrastructure layers.
Proxies often sit between client and server
MDN's HTTP overview notes that real client-server systems commonly include proxies/gateways/caches between the user agent and origin server.
A modern path can be:
client
-> CDN/WAF
-> reverse proxy/API gateway
-> load balancer
-> application server
These intermediaries can provide:
- TLS termination
- caching
- routing
- rate limiting
- DDoS filtering
- authentication checks
The client does not need to know which backend instance ultimately handled the request.
Load balancing distributes traffic
When one application instance is not enough, several servers can run behind a load balancer.
The balancer chooses a healthy backend based on policies such as:
- round robin
- least connections
- weighted routing
- geographic/latency routing
- health state
Cloudflare's current load-balancing reference architecture emphasizes availability, traffic distribution, failover, and scalability as central goals of this layer.
Stateless servers are easier to replicate
If any replica can handle any request, scaling and failover are easier.
This does not mean the system has no state.
Durable state usually lives in shared systems such as:
- database
- cache
- object storage
- durable queue
The important idea is that one HTTP server process should not be the only place critical user state exists.
Sessions can still work in a stateless server fleet
Authentication/session state can be handled through:
- signed/encrypted client-side cookies/tokens
- shared session store
If session data lives only in one process's memory, the system may need sticky routing and loses some failover flexibility.
Sticky sessions can be valid, but use them deliberately rather than accidentally.
Authentication and authorization belong on the server
The client may send credentials such as a session cookie or bearer token.
The server verifies identity, then authorizes the actual resource/action.
For:
GET /orders/o42
the backend must verify the caller can access o42.
Do not trust user IDs, role fields, prices, or tenant ownership supplied by the client as authoritative business state.
The API is the client-server contract
The contract can use technologies such as:
- REST/HTTP
- GraphQL
- gRPC
- WebSocket protocols
A good contract defines:
- request shapes
- response shapes
- errors
- authentication
- compatibility/versioning
- pagination
- retry/idempotency semantics
The client should depend on this contract rather than server implementation details such as table names or internal classes.
Client and server versions can be different
Web clients can often update immediately.
Mobile, desktop, and third-party clients may stay on old versions for months.
Servers therefore need backward-compatible evolution:
add new optional field
keep old behavior
migrate clients
remove old contract only after safe window
Do not design an API that requires every client to update at the same instant as the backend.
Caching can exist on both sides
Clients can cache responses locally.
Intermediaries/CDNs can cache shared content.
Servers can use internal caches.
HTTP headers such as Cache-Control and validators such as ETag help define cache behavior.
Be extremely careful with personalized data. A shared cache that ignores authentication/tenant dimensions can leak one user's response to another.
A client can also be another server
Microservices are still client-server interactions.
When the checkout service calls inventory:
checkout = client
inventory = server
For the next call, roles can reverse.
This means internal services need the same engineering discipline:
- identity
- authorization
- timeout
- retry rules
- idempotency
- observability
“Internal network” does not remove network failure.
Request-response is not the only client-server interaction
Some systems keep a long-lived connection.
Examples:
- WebSocket
- server-sent events
- gRPC streaming
- WebTransport
The roles still exist, but communication can continue after the initial handshake.
For example, a WebSocket server can push messages after the client established the connection.
Do not confuse long-lived transport with durable message history; disconnected clients still need recovery semantics.
Client-side rendering changes presentation, not trust
A single-page app may execute most UI logic in the browser.
That does not move backend authority to the client.
The browser can decide:
- what screen to render
- local interaction state
- optimistic UI
The server still owns:
- authorization
- durable writes
- shared truth
- secret provider credentials
Assume an attacker can rewrite the entire client application.
Thin client vs thick client is a spectrum
A thin client delegates most business computation/state to the server.
A thick/rich client may handle:
- offline state
- local database
- complex UI logic
- optimistic workflows
Offline-capable clients require synchronization design:
- conflict resolution
- versioning
- idempotent replay
- server-authoritative merge rules
The more state the client owns, the more explicit synchronization becomes.
Servers should not trust client clocks
Client device time can be wrong or manipulated.
For authoritative ordering and business timestamps, prefer server-controlled time/sequence information.
Client timestamps can still be useful metadata, but do not use them as the sole proof of when a transaction legally/business-wise occurred.
Scaling the client and server are different problems
A web client scales naturally because computation runs on each user's device.
Servers share central capacity across users and must manage:
- CPU
- memory
- DB connections
- sockets
- provider quotas
Server scaling therefore needs load balancing, caches, queues, database capacity, and backpressure.
Client performance focuses more on bundle size, rendering, device/network quality, and local storage.
Failure handling belongs in the contract
Clients must know what to do with failures:
401 -> authenticate
409 -> conflict
429 -> rate policy/backoff
503 -> transient service unavailable
Retries should be bounded and only used when operations are safe.
A client timeout does not prove a server mutation failed; idempotency keys/status lookups may be required to resolve ambiguous outcomes.
Observability should connect both perspectives
Server metrics can show API latency and errors.
Client telemetry can show:
- DNS/connect/TLS time
- time to first byte
- request failure on real networks
- app version/device patterns
Distributed trace/correlation IDs can connect server-side processing across dependencies.
Do not assume “server p95 is fine” means every user has a fast experience.
A practical architecture
For a typical SaaS app:
web/mobile client
|
v
CDN / edge / WAF
|
v
API load balancer
|
v
stateless app replicas
| | |
database cache queue/object storage
The exact technologies matter less than clear ownership:
- client owns presentation/local interaction
- edge owns delivery/protection/caching where safe
- server owns business policy/authorization
- durable stores own authoritative state
Client-server checklist
Before designing a client-server system, ask:
- Which data is authoritative on the server?
- What can be safely stored/cached on the client?
- How is caller identity authenticated?
- Where is resource-level authorization enforced?
- How are old client versions supported?
- Can any healthy server replica handle a request?
- Which state requires a shared/durable store?
- Which responses may be cached by shared intermediaries?
- How do clients retry mutations safely?
- How are latency/failures observed from both client and server?
Client-server architecture works because it creates a stable responsibility boundary: clients express intent and presentation needs; servers enforce shared rules and authoritative state. The system scales when that boundary remains secure, compatible, and observable as both sides evolve independently.
References
BUILDING AN AI PRODUCT?
Talk to Lofingo about custom autonomous AI agents, enterprise workflow automation, and scalable software systems.
EXPLORE AI SERVICES →