Definition
Client-server architecture is one of the foundational constraints of REST. It mandates a clear separation of concerns between the client (the user interface or application making requests) and the server (the system providing resources and processing logic). This separation means the client doesn’t need to know anything about data storage, business logic, or server infrastructure, while the server doesn’t need to know anything about user interfaces, user state, or how the client will use the data.
The beauty of this separation is that it allows client and server to evolve independently. You can completely redesign your mobile app UI without touching the server. You can scale your backend infrastructure, switch databases, or rewrite business logic without breaking client applications. As long as the contract (the API interface) remains consistent, both sides can change freely.
In REST APIs, this constraint is enforced through a uniform interface (HTTP methods, status codes, resource URLs) that acts as the bridge between client and server. The client sends requests, the server sends responses, and neither side needs to understand the other’s internal workings.
Example
Netflix Streaming: The Netflix mobile app (client) sends requests to Netflix’s API servers. The client handles the UI, video playback, and user interactions. The server manages user authentication, content catalogs, recommendations, and video encoding. Netflix can completely redesign their app UI (which they do regularly) without changing server code. They can also scale their server infrastructure globally, switch CDN providers, or optimize their recommendation algorithms without releasing new app versions.
E-commerce Platform (Amazon): Your Amazon shopping app is the client - it displays products, manages your cart UI, and handles navigation. Amazon’s servers handle inventory management, pricing, order processing, and payment. When Amazon redesigns their mobile app, their servers keep working with older app versions. When they upgrade their warehouse management systems, your app continues to function without updates.
Weather App: A weather app (client) calls OpenWeatherMap API (server). The app decides how to display weather data - charts, maps, notifications. The server manages weather data collection, storage, forecasting models. The app developer can create iOS, Android, and web versions (different clients) all using the same server API. OpenWeatherMap can improve their forecasting models without breaking any client apps.
Banking Application: Your mobile banking app is the client that presents transactions, allows transfers, and displays balances. The bank’s servers manage accounts, process transactions, enforce security policies, and maintain ledgers. The bank can migrate from mainframe to cloud infrastructure (server changes) without forcing customers to update their apps. Customers can use web, mobile, or ATM interfaces (different clients) all talking to the same backend servers.
Analogy
Restaurant with Table Service: You (the client) sit at a table and make requests from a menu. The kitchen (server) prepares your food. You don’t need to know how the kitchen is organized, what equipment they use, or where they source ingredients. The kitchen doesn’t need to know how you’ll arrange the food on your plate or how you’ll eat it. The menu (API contract) is the interface - you order “Caesar salad,” and you get Caesar salad, regardless of whether the kitchen has a new chef or new appliances.
ATM and Bank: An ATM (client) is the interface you use to withdraw cash. The bank’s systems (server) manage your account balance, transaction logs, and security. The ATM doesn’t store your balance or process the transaction - it just sends your request to the server. The bank can upgrade their database, switch to new security systems, or consolidate branches without replacing ATMs. Different ATM brands (different clients) can all access the same bank systems through the standard interface.
Library and Catalog System: You use the library’s computer terminal (client) to search for books. The library’s database system (server) stores book locations, availability, and member records. The terminal just displays results and accepts input. The library can upgrade their database, change storage systems, or reorganize shelves (server changes) without changing the terminals. They can add mobile apps or website search (new clients) using the same backend database.
Electric Power Grid: Your devices (clients) plug into wall outlets and request electricity. The power grid (server) generates, distributes, and manages electricity supply. Your devices don’t need to know if power comes from coal, solar, or nuclear plants. The utility company can change generation methods, upgrade infrastructure, or add capacity without you replacing your devices. Different devices (laptop, refrigerator, phone charger - different clients) all use the same standardized interface (the outlet).
Code Example
// Client-Server separation in practice
// The client and server are completely independent
// ===== CLIENT CODE (React App) =====
// Client handles UI, rendering, user interactions
// Client does NOT know about database, business logic, or server infrastructure
import React, { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Client makes a request to server
// It only knows the API endpoint, not how data is stored or processed
fetch(`https://api.example.com/users/${userId}`, {
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`,
'Accept': 'application/json'
}
})
.then(response => response.json())
.then(data => {
setUser(data);
setLoading(false);
})
.catch(error => {
console.error('Error fetching user:', error);
setLoading(false);
});
}, [userId]);
// Client decides HOW to display data (UI concern)
if (loading) return <div>Loading...</div>;
return (
<div className="user-profile">
<img src={user.avatar} alt={user.name} />
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}
// ===== SERVER CODE (Node.js/Express) =====
// Server handles data storage, business logic, authentication
// Server does NOT know about UI, how clients will display data, or user preferences
const express = require('express');
const app = express();
const db = require('./database'); // Server's internal concern
const auth = require('./auth'); // Server's internal concern
app.get('/users/:id', auth.requireAuth, async (req, res) => {
try {
// Server handles authentication (business logic)
const userId = req.params.id;
// Verify user has permission to view this profile
if (req.user.id !== userId && !req.user.isAdmin) {
return res.status(403).json({ error: 'Forbidden' });
}
// Server handles data retrieval (data storage concern)
// Client doesn't know if this is PostgreSQL, MongoDB, or microservices
const user = await db.query(
'SELECT id, name, email, avatar FROM users WHERE id = $1',
[userId]
);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
// Server sends data representation
// It doesn't care if client is mobile, web, or CLI
res.json({
id: user.id,
name: user.name,
email: user.email,
avatar: user.avatar
});
} catch (error) {
console.error('Server error:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// ===== BENEFITS OF SEPARATION =====
// 1. Independent evolution:
// - Client can switch from React to Vue without server changes
// - Server can switch from PostgreSQL to MongoDB without client changes
// 2. Multiple clients:
// - iOS app, Android app, web app all use same server API
// - Each client optimizes UI for its platform
// 3. Scalability:
// - Server can be replicated, load-balanced, cached
// - Client doesn't need to know about server topology
// 4. Security:
// - Client never sees database credentials or business logic
// - Server enforces authorization regardless of client
Diagram
graph TB
subgraph Client["Client (User Interface Concerns)"]
UI[UI Rendering]
State[State Management]
UX[User Experience]
Display[Data Display Logic]
end
subgraph Interface["API Interface (Contract)"]
HTTP[HTTP Methods
GET, POST, PUT, DELETE]
URLs[Resource URLs
/users, /products]
Headers[Headers
Auth, Content-Type]
Status[Status Codes
200, 404, 500]
end
subgraph Server["Server (Data & Logic Concerns)"]
Auth[Authentication]
BizLogic[Business Logic]
DB[Data Storage]
Processing[Data Processing]
end
Client <-->|Requests| Interface
Interface <-->|Responses| Server
subgraph Benefits["Separation Benefits"]
Ind[Independent
Evolution]
Multi[Multiple Clients
One Server]
Scale[Scalable
Infrastructure]
Secure[Security
Isolation]
end
Interface -.-> Benefits
Security Notes
CRITICAL: Never trust client input; validate and sanitize on the server. Client-server separation creates security boundaries that must be enforced.
Input Validation & Trust Boundaries:
- Server-side validation mandatory: Never rely on client-side validation alone
- Validate all inputs: Sanitize data from client to prevent injection attacks
- No client-side business logic: Security logic implemented on client can be bypassed or inspected
- Assume client is hostile: Don’t trust any data originating from the client
Authentication & Authorization:
- Authenticate every request: Server must verify identity independently (no reliance on client state)
- Server-side sessions: Use server-side validation even if client sends valid-looking tokens
- Never expose credentials: Store secrets and API keys server-side only
- Token validation: Verify token signatures and expiration on every request
Transport Security:
- HTTPS mandatory: Encrypt all client-server communication
- TLS 1.2 minimum: Enforce modern TLS versions
- Certificate validation: Verify server certificates to prevent MITM attacks
Information Disclosure Prevention:
- Never expose server internals: Don’t return database error messages, stack traces, or internal implementation details
- Hide database schemas: Don’t expose database structure in error messages or responses
- Hide file paths: Don’t reveal server filesystem paths or structure
- Hide service names: Don’t expose internal microservice names or architecture
Rate Limiting & Abuse Prevention:
- Rate limiting: Implement per-client, per-endpoint rate limits
- Request throttling: Throttle expensive operations on the server
- Account lockout: Lock accounts after N failed authentication attempts
- DDoS protection: Use cloudflare, rate limiting, and request validation
CORS & Cross-Origin Security:
- CORS policy: Use proper CORS headers to control which clients can access
- Specific origins: Never use
Access-Control-Allow-Origin: *with credentials - Preflight validation: Properly validate OPTIONS preflight requests
Response Security:
- Content-Security-Policy: Implement CSP headers to prevent XSS attacks
- No sensitive data in responses: Exclude PII, internal IDs, or sensitive information
- Consistent response format: Standardize error responses to avoid information leakage
Best Practices
- Clear API contract: Define the interface (endpoints, methods, payloads) explicitly using OpenAPI or similar standards
- Never trust the client: Validate all inputs, enforce business rules, and check permissions server-side
- Stateless communication: Server shouldn’t store client state between requests (use tokens, not sessions)
- Version your API: Allow server to evolve while supporting older clients gracefully
- Return consistent responses: Use standard formats (JSON, XML) and HTTP status codes
- Implement proper error handling: Return meaningful error messages without exposing server internals
- Support multiple clients: Design APIs to serve web, mobile, and other platforms without client-specific endpoints
- Enable independent deployment: Client and server should deploy separately without coordination
- Document thoroughly: Provide clear API documentation so client developers understand the contract
- Use API gateways: Centralize cross-cutting concerns (authentication, rate limiting, logging) in a gateway layer
Common Mistakes
Tight coupling: Creating endpoints tailored to specific UI screens (/getHomePageData) instead of resource-oriented APIs (/products, /users).
Client-side business logic: Implementing validation or authorization rules in client code that should be server-side.
Exposing server internals: Returning database error messages or internal stack traces to clients.
Stateful sessions: Storing user state on the server tied to client sessions violates REST statelessness and limits scalability.
Client-specific endpoints: Creating separate APIs for mobile vs web instead of one unified API that serves all clients.
Breaking changes: Modifying API contracts without versioning, forcing all clients to update simultaneously.
Trusting client data: Accepting client-calculated totals, permissions, or sensitive data without server-side verification.
No separation of concerns: Mixing presentation logic (HTML rendering) with data APIs, preventing headless or mobile clients.